Pārlūkot izejas kodu

Upgrade to Laravel 9

Will Browning 3 gadi atpakaļ
vecāks
revīzija
43e094ac93
92 mainītis faili ar 2146 papildinājumiem un 2262 dzēšanām
  1. 4 3
      .husky/_/husky.sh
  2. 1 1
      .php-cs-fixer.php
  3. 1 1
      README.md
  4. 10 10
      SELF-HOSTING.md
  5. 3 3
      app/Console/Commands/CreateUser.php
  6. 1 1
      app/Console/Commands/ReceiveEmail.php
  7. 46 0
      app/Console/Commands/UpdateAppVersion.php
  8. 26 32
      app/CustomMailDriver/CustomMailManager.php
  9. 167 0
      app/CustomMailDriver/CustomMailer.php
  10. 0 157
      app/CustomMailDriver/CustomSendmailTransport.php
  11. 0 195
      app/CustomMailDriver/CustomSmtpTransport.php
  12. 40 0
      app/CustomMailDriver/Mime/Crypto/AlreadyEncrypted.php
  13. 302 0
      app/CustomMailDriver/Mime/Crypto/OpenPGPEncrypter.php
  14. 25 0
      app/CustomMailDriver/Mime/Encoder/RawContentEncoder.php
  15. 125 0
      app/CustomMailDriver/Mime/Part/EncryptedPart.php
  16. 48 0
      app/CustomMailDriver/Mime/Part/InlineImagePart.php
  17. 106 0
      app/CustomMailDriver/Mime/Part/TextPart.php
  18. 13 0
      app/Exceptions/CouldNotGetVersionException.php
  19. 0 74
      app/Helpers/AlreadyEncryptedSigner.php
  20. 71 0
      app/Helpers/GitVersionHelper.php
  21. 0 433
      app/Helpers/OpenPGPSigner.php
  22. 1 3
      app/Http/Controllers/AliasExportController.php
  23. 6 4
      app/Http/Controllers/Api/AppVersionController.php
  24. 4 4
      app/Http/Controllers/Auth/RegisterController.php
  25. 11 32
      app/Http/Controllers/Auth/WebauthnController.php
  26. 3 1
      app/Http/Controllers/Controller.php
  27. 3 0
      app/Http/Controllers/SettingController.php
  28. 2 1
      app/Http/Kernel.php
  29. 7 2
      app/Http/Middleware/TrustProxies.php
  30. 1 1
      app/Http/Requests/EditDefaultRecipientRequest.php
  31. 1 1
      app/Http/Requests/StoreAliasRecipientRequest.php
  32. 2 2
      app/Http/Requests/StoreAliasRequest.php
  33. 3 3
      app/Http/Requests/StoreDomainRequest.php
  34. 2 2
      app/Http/Requests/StoreRecipientRequest.php
  35. 1 1
      app/Http/Requests/StoreReorderRuleRequest.php
  36. 2 2
      app/Http/Requests/StoreUsernameRequest.php
  37. 4 1
      app/Jobs/DeleteAccount.php
  38. 31 67
      app/Mail/ForwardEmail.php
  39. 27 43
      app/Mail/ReplyToEmail.php
  40. 27 43
      app/Mail/SendFromEmail.php
  41. 13 3
      app/Mail/TokenExpiringSoon.php
  42. 4 1
      app/Models/Alias.php
  43. 2 1
      app/Models/DeletedUsername.php
  44. 3 1
      app/Models/Domain.php
  45. 2 1
      app/Models/EmailData.php
  46. 3 1
      app/Models/FailedDelivery.php
  47. 6 3
      app/Models/Recipient.php
  48. 2 1
      app/Models/Rule.php
  49. 7 3
      app/Models/User.php
  50. 3 1
      app/Models/Username.php
  51. 3 2
      app/Notifications/CustomVerifyEmail.php
  52. 6 26
      app/Notifications/DefaultRecipientUpdated.php
  53. 6 24
      app/Notifications/DisallowedReplySendAttempt.php
  54. 6 24
      app/Notifications/DomainMxRecordsInvalid.php
  55. 6 24
      app/Notifications/DomainUnverifiedForSending.php
  56. 5 26
      app/Notifications/FailedDeliveryNotification.php
  57. 7 2
      app/Notifications/GpgKeyExpired.php
  58. 6 24
      app/Notifications/NearBandwidthLimit.php
  59. 6 26
      app/Notifications/SpamReplySendAttempt.php
  60. 6 26
      app/Notifications/UsernameReminder.php
  61. 0 3
      app/Providers/AppServiceProvider.php
  62. 3 2
      app/Providers/CustomMailServiceProvider.php
  63. 10 0
      app/Providers/EventServiceProvider.php
  64. 1 0
      app/Providers/HelperServiceProvider.php
  65. 5 5
      app/Providers/RouteServiceProvider.php
  66. 3 23
      app/Services/Webauthn.php
  67. 2 5
      app/Traits/CheckUserRules.php
  68. 10 14
      composer.json
  69. 213 423
      composer.lock
  70. 25 38
      config/app.php
  71. 10 2
      config/broadcasting.php
  72. 3 3
      config/cache.php
  73. 13 1
      config/database.php
  74. 5 2
      config/filesystems.php
  75. 29 2
      config/logging.php
  76. 61 79
      config/mail.php
  77. 1 0
      config/services.php
  78. 0 60
      config/version.yml
  79. 69 12
      config/webauthn.php
  80. 31 0
      database/migrations/2022_06_29_103709_update_email_type_in_failed_deliveries_table.php
  81. 152 110
      package-lock.json
  82. 1 1
      package.json
  83. 10 9
      resources/js/components/WebauthnKeys.vue
  84. 10 0
      resources/js/webauthn.js
  85. 1 1
      resources/views/layouts/app.blade.php
  86. 4 4
      resources/views/settings/show.blade.php
  87. 25 5
      resources/views/vendor/webauthn/authenticate.blade.php
  88. 12 4
      resources/views/vendor/webauthn/register.blade.php
  89. 127 72
      routes/api.php
  90. 79 37
      routes/web.php
  91. 1 1
      tests/Feature/Api/AppVersionTest.php
  92. 1 1
      tests/Feature/Api/RulesTest.php

+ 4 - 3
.husky/_/husky.sh

@@ -1,7 +1,9 @@
 #!/bin/sh
 #!/bin/sh
 if [ -z "$husky_skip_init" ]; then
 if [ -z "$husky_skip_init" ]; then
   debug () {
   debug () {
-    [ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1"
+    if [ "$HUSKY_DEBUG" = "1" ]; then
+      echo "husky (debug) - $1"
+    fi
   }
   }
 
 
   readonly hook_name="$(basename "$0")"
   readonly hook_name="$(basename "$0")"
@@ -23,8 +25,7 @@ if [ -z "$husky_skip_init" ]; then
 
 
   if [ $exitCode != 0 ]; then
   if [ $exitCode != 0 ]; then
     echo "husky - $hook_name hook exited with code $exitCode (error)"
     echo "husky - $hook_name hook exited with code $exitCode (error)"
-    exit $exitCode
   fi
   fi
 
 
-  exit 0
+  exit $exitCode
 fi
 fi

+ 1 - 1
.php-cs-fixer.php

@@ -13,7 +13,7 @@ $finder = Symfony\Component\Finder\Finder::create()
 $config = new PhpCsFixer\Config();
 $config = new PhpCsFixer\Config();
 
 
 return $config->setRules([
 return $config->setRules([
-        '@PSR2' => true,
+        '@PSR12' => true,
         'array_syntax' => ['syntax' => 'short'],
         'array_syntax' => ['syntax' => 'short'],
         'ordered_imports' => ['sort_algorithm' => 'alpha'],
         'ordered_imports' => ['sort_algorithm' => 'alpha'],
         'no_unused_imports' => true,
         'no_unused_imports' => true,

+ 1 - 1
README.md

@@ -410,7 +410,7 @@ If you've forgotten your username you can request a reminder by entering your em
 
 
 Please use the backup code that you were shown when you enabled 2FA.
 Please use the backup code that you were shown when you enabled 2FA.
 
 
-5. Errors with U2F device
+5. Errors with hardware security key
 
 
 If you have a YubiKey and are using Windows and have an issue with your personal password/PIN you may need to reset the key using the YubiKey manager software.
 If you have a YubiKey and are using Windows and have an issue with your personal password/PIN you may need to reset the key using the YubiKey manager software.
 
 

+ 10 - 10
SELF-HOSTING.md

@@ -358,7 +358,7 @@ location = /robots.txt  { access_log off; log_not_found off; }
 error_page 404 /index.php;
 error_page 404 /index.php;
 
 
 location ~ \.php$ {
 location ~ \.php$ {
-    fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
+    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
     fastcgi_index index.php;
     fastcgi_index index.php;
     fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
     fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
     include fastcgi_params;
     include fastcgi_params;
@@ -379,9 +379,9 @@ We won't restart nginx yet because it won't be able to find the SSL certificates
 
 
 ## Installing PHP
 ## Installing PHP
 
 
-We're going to install the latest version of PHP at the time of writing this - version 7.4
+We're going to install the latest version of PHP at the time of writing this - version 8.1
 
 
-First we need to add the following repository so we can install PHP8.0.
+First we need to add the following repository so we can install php8.1.
 
 
 ```bash
 ```bash
 sudo apt install software-properties-common
 sudo apt install software-properties-common
@@ -389,21 +389,21 @@ sudo add-apt-repository ppa:ondrej/php
 sudo apt update
 sudo apt update
 ```
 ```
 
 
-Install PHP8.0 and check the version.
+Install php8.1 and check the version.
 
 
 ```bash
 ```bash
-sudo apt install php8.0-fpm
-php-fpm8.0 -v
+sudo apt install php8.1-fpm
+php-fpm8.1 -v
 ```
 ```
 
 
 Install some required extensions:
 Install some required extensions:
 
 
 ```bash
 ```bash
-sudo apt install php8.0-common php8.0-mysql php8.0-dev php8.0-gmp php8.0-mbstring php8.0-dom php8.0-gd php8.0-imagick php8.0-opcache php8.0-soap php8.0-zip php8.0-cli php8.0-curl php8.0-mailparse php8.0-gnupg php8.0-redis -y
+sudo apt install php8.1-common php8.1-mysql php8.1-dev php8.1-gmp php8.1-mbstring php8.1-dom php8.1-gd php8.1-imagick php8.1-opcache php8.1-soap php8.1-zip php8.1-cli php8.1-curl php8.1-mailparse php8.1-gnupg php8.1-redis -y
 ```
 ```
 
 
 ```bash
 ```bash
-sudo nano /etc/php/8.0/fpm/pool.d/www.conf
+sudo nano /etc/php/8.1/fpm/pool.d/www.conf
 ```
 ```
 
 
 ```
 ```
@@ -413,10 +413,10 @@ listen.owner = johndoe
 listen.group = johndoe
 listen.group = johndoe
 ```
 ```
 
 
-Restart php8.0-fpm to reflect the changes.
+Restart php8.1-fpm to reflect the changes.
 
 
 ```bash
 ```bash
-sudo service php8.0-fpm restart
+sudo service php8.1-fpm restart
 ```
 ```
 
 
 ## Let's Encrypt
 ## Let's Encrypt

+ 3 - 3
app/Console/Commands/CreateUser.php

@@ -55,14 +55,14 @@ class CreateUser extends Command
                 'regex:/^[a-zA-Z0-9]*$/',
                 'regex:/^[a-zA-Z0-9]*$/',
                 'max:20',
                 'max:20',
                 'unique:usernames,username',
                 'unique:usernames,username',
-                new NotDeletedUsername
+                new NotDeletedUsername()
             ],
             ],
             'email' => [
             'email' => [
                 'required',
                 'required',
                 'email:rfc,dns',
                 'email:rfc,dns',
                 'max:254',
                 'max:254',
-                new RegisterUniqueRecipient,
-                new NotLocalRecipient
+                new RegisterUniqueRecipient(),
+                new NotLocalRecipient()
             ],
             ],
         ]);
         ]);
 
 

+ 1 - 1
app/Console/Commands/ReceiveEmail.php

@@ -462,7 +462,7 @@ class ReceiveEmail extends Command
 
 
     protected function getParser($file)
     protected function getParser($file)
     {
     {
-        $parser = new Parser;
+        $parser = new Parser();
 
 
         // Fix some edge cases in from name e.g. "\" John Doe \"" <johndoe@example.com>
         // Fix some edge cases in from name e.g. "\" John Doe \"" <johndoe@example.com>
         $parser->addMiddleware(function ($mimePart, $next) {
         $parser->addMiddleware(function ($mimePart, $next) {

+ 46 - 0
app/Console/Commands/UpdateAppVersion.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Helpers\GitVersionHelper;
+use Illuminate\Console\Command;
+
+class UpdateAppVersion extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'anonaddy:update-app-version';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Updates the cached app version';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $version = GitVersionHelper::cacheFreshVersion();
+        $this->info("AnonAddy version: {$version}");
+
+        return 0;
+    }
+}

+ 26 - 32
app/CustomMailDriver/CustomMailManager.php

@@ -3,51 +3,45 @@
 namespace App\CustomMailDriver;
 namespace App\CustomMailDriver;
 
 
 use Illuminate\Mail\MailManager;
 use Illuminate\Mail\MailManager;
+use InvalidArgumentException;
 
 
 class CustomMailManager extends MailManager
 class CustomMailManager extends MailManager
 {
 {
     /**
     /**
-     * Create an instance of the Sendmail Swift Transport driver.
+     * Resolve the given mailer.
      *
      *
-     * @param  array  $config
-     * @return \Swift_SendmailTransport
+     * @param string $name
+     * @return Mailer
      */
      */
-    protected function createSendmailTransport(array $config)
+    protected function resolve($name): CustomMailer
     {
     {
-        return new CustomSendmailTransport(
-            $config['path'] ?? $this->app['config']->get('mail.sendmail')
-        );
-    }
+        $config = $this->getConfig($name);
 
 
-    /**
-     * Create an instance of the SMTP Swift Transport driver.
-     *
-     * @param  array  $config
-     * @return \Swift_SmtpTransport
-     */
-    protected function createSmtpTransport(array $config)
-    {
-        // The Swift SMTP transport instance will allow us to use any SMTP backend
-        // for delivering mail such as Sendgrid, Amazon SES, or a custom server
-        // a developer has available. We will just pass this configured host.
-        $transport = new CustomSmtpTransport(
-            $config['host'],
-            $config['port']
+        if ($config === null) {
+            throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
+        }
+
+        // Once we have created the mailer instance we will set a container instance
+        // on the mailer. This allows us to resolve mailer classes via containers
+        // for maximum testability on said classes instead of passing Closures.
+        $mailer = new CustomMailer(
+            $name,
+            $this->app['view'],
+            $this->createSymfonyTransport($config),
+            $this->app['events']
         );
         );
 
 
-        if (! empty($config['encryption'])) {
-            $transport->setEncryption($config['encryption']);
+        if ($this->app->bound('queue')) {
+            $mailer->setQueue($this->app['queue']);
         }
         }
 
 
-        // Once we have the transport we will check for the presence of a username
-        // and password. If we have it we will set the credentials on the Swift
-        // transporter instance so that we'll properly authenticate delivery.
-        if (isset($config['username'])) {
-            $transport->setUsername($config['username']);
-
-            $transport->setPassword($config['password']);
+        // Next we will set all of the global addresses on this mailer, which allows
+        // for easy unification of all "from" addresses as well as easy debugging
+        // of sent messages since these will be sent to a single email address.
+        foreach (['from', 'reply_to', 'to', 'return_path'] as $type) {
+            $this->setGlobalAddress($mailer, $config, $type);
         }
         }
 
 
-        return $this->configureSmtpTransport($transport, $config);
+        return $mailer;
     }
     }
 }
 }

+ 167 - 0
app/CustomMailDriver/CustomMailer.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace App\CustomMailDriver;
+
+use App\CustomMailDriver\Mime\Crypto\AlreadyEncrypted;
+use App\CustomMailDriver\Mime\Crypto\OpenPGPEncrypter;
+use App\Models\PostfixQueueId;
+use App\Models\Recipient;
+use App\Notifications\GpgKeyExpired;
+use Illuminate\Contracts\Mail\Mailable as MailableContract;
+use Illuminate\Database\QueryException;
+use Illuminate\Mail\Mailer;
+use Illuminate\Mail\SentMessage;
+use Illuminate\Support\Str;
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Exception\RuntimeException;
+use Symfony\Component\Mime\Crypto\DkimOptions;
+use Symfony\Component\Mime\Crypto\DkimSigner;
+use Symfony\Component\Mime\Email;
+
+class CustomMailer extends Mailer
+{
+    /**
+     * Send a new message using a view.
+     *
+     * @param MailableContract|string|array  $view
+     * @param  array  $data
+     * @param  \Closure|string|null  $callback
+     * @return SentMessage|null
+     */
+    public function send($view, array $data = [], $callback = null)
+    {
+        if ($view instanceof MailableContract) {
+            return $this->sendMailable($view);
+        }
+
+        // First we need to parse the view, which could either be a string or an array
+        // containing both an HTML and plain text versions of the view which should
+        // be used when sending an e-mail. We will extract both of them out here.
+        [$view, $plain, $raw] = $this->parseView($view);
+
+        $data['message'] = $message = $this->createMessage();
+
+        // Once we have retrieved the view content for the e-mail we will set the body
+        // of this message using the HTML type, which will provide a simple wrapper
+        // to creating view based emails that are able to receive arrays of data.
+        if (! is_null($callback)) {
+            $callback($message);
+        }
+
+        $this->addContent($message, $view, $plain, $raw, $data);
+
+        // If a global "to" address has been set, we will set that address on the mail
+        // message. This is primarily useful during local development in which each
+        // message should be delivered into a single mail address for inspection.
+        if (isset($this->to['address'])) {
+            $this->setGlobalToAndRemoveCcAndBcc($message);
+        }
+
+        // Next we will determine if the message should be sent. We give the developer
+        // one final chance to stop this message and then we will send it to all of
+        // its recipients. We will then fire the sent event for the sent message.
+        $symfonyMessage = $message->getSymfonyMessage();
+
+        // OpenPGPEncrypter
+        if (isset($data['fingerprint']) && $data['fingerprint']) {
+            $recipient = Recipient::find($data['recipientId']);
+
+            try {
+                $encrypter = new OpenPGPEncrypter(config('anonaddy.signing_key_fingerprint'), $data['fingerprint'], "~/.gnupg");
+            } catch (RuntimeException $e) {
+                info($e->getMessage());
+                $encrypter = null;
+
+                $recipient->update(['should_encrypt' => false]);
+
+                $recipient->notify(new GpgKeyExpired());
+            }
+
+            if ($encrypter) {
+                $symfonyMessage = $encrypter->encrypt($symfonyMessage);
+            }
+        }
+
+        // Already encrypted
+        if (isset($data['encryptedParts']) && $data['encryptedParts']) {
+            $symfonyMessage = (new AlreadyEncrypted($data['encryptedParts']))->update($symfonyMessage);
+        }
+
+        // DkimSigner only for forwards, replies and sends...
+        if (isset($data['needsDkimSignature']) && $data['needsDkimSignature']) {
+            $dkimSigner = new DkimSigner(config('anonaddy.dkim_signing_key'), $data['aliasDomain'], config('anonaddy.dkim_selector'));
+
+            $options = (new DkimOptions())->headersToIgnore([
+                'List-Unsubscribe',
+                'Return-Path',
+                'Feedback-ID',
+                'Content-Type',
+                'Content-Description',
+                'Content-Disposition',
+                'Content-Transfer-Encoding',
+                'MIME-Version',
+                'Alias-To',
+                'X-AnonAddy-Authentication-Results',
+                'X-AnonAddy-Original-Sender',
+                'X-AnonAddy-Original-Envelope-From',
+                'X-AnonAddy-Original-From-Header',
+                'X-AnonAddy-Original-To',
+                'In-Reply-To',
+                'References',
+                'From',
+                'To',
+                'Message-ID',
+                'Subject',
+                'Date'
+            ])->toArray();
+            $signedEmail = $dkimSigner->sign($symfonyMessage, $options);
+            $symfonyMessage->setHeaders($signedEmail->getHeaders());
+        }
+
+        if ($this->shouldSendMessage($symfonyMessage, $data)) {
+            $symfonySentMessage = $this->sendSymfonyMessage($symfonyMessage);
+
+            if ($symfonySentMessage) {
+                $sentMessage = new SentMessage($symfonySentMessage);
+
+                $this->dispatchSentEvent($sentMessage, $data);
+
+                try {
+                    // Get Postfix Queue ID and save in DB
+                    $id = str_replace("\r\n", "", Str::after($sentMessage->getDebug(), 'Ok: queued as '));
+
+                    PostfixQueueId::create([
+                        'queue_id' => $id
+                    ]);
+                } catch (QueryException $e) {
+                    // duplicate entry
+                    //Log::info('Failed to save Postfix Queue ID: ' . $id);
+                }
+
+                return $sentMessage;
+            }
+        }
+    }
+
+    /**
+     * Send a Symfony Email instance.
+     *
+     * @param  \Symfony\Component\Mime\Email  $message
+     * @return \Symfony\Component\Mailer\SentMessage|null
+     */
+    protected function sendSymfonyMessage(Email $message)
+    {
+        try {
+            $envelopeMessage = clone $message;
+            // This allows us to have the To: header set as the alias whilst still delivering to the correct RCPT TO.
+            if ($aliasTo = $message->getHeaders()->get('Alias-To')) {
+                $message->to($aliasTo->getValue());
+                $message->getHeaders()->remove('Alias-To');
+            }
+
+            return $this->transport->send($message, Envelope::create($envelopeMessage));
+        } finally {
+            //
+        }
+    }
+}

+ 0 - 157
app/CustomMailDriver/CustomSendmailTransport.php

@@ -1,157 +0,0 @@
-<?php
-
-namespace App\CustomMailDriver;
-
-use Swift_AddressEncoderException;
-use Swift_DependencyContainer;
-use Swift_Events_SendEvent;
-use Swift_Mime_SimpleMessage;
-use Swift_Transport_SendmailTransport;
-use Swift_TransportException;
-
-class CustomSendmailTransport extends Swift_Transport_SendmailTransport
-{
-    /**
-     * Create a new SendmailTransport, optionally using $command for sending.
-     *
-     * @param string $command
-     */
-    public function __construct($command = '/usr/sbin/sendmail -bs')
-    {
-        \call_user_func_array(
-            [$this, 'Swift_Transport_SendmailTransport::__construct'],
-            Swift_DependencyContainer::getInstance()
-                ->createDependenciesFor('transport.sendmail')
-        );
-
-        $this->setCommand($command);
-    }
-
-    /**
-     * Send the given Message.
-     *
-     * Recipient/sender data will be retrieved from the Message API.
-     * The return value is the number of recipients who were accepted for delivery.
-     *
-     * @param string[] $failedRecipients An array of failures by-reference
-     *
-     * @return int
-     */
-    public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
-    {
-        if (!$this->isStarted()) {
-            $this->start();
-        }
-
-        $sent = 0;
-        $failedRecipients = (array) $failedRecipients;
-
-        if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) {
-            $this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
-            if ($evt->bubbleCancelled()) {
-                return 0;
-            }
-        }
-
-        if (!$reversePath = $this->getReversePath($message)) {
-            $this->throwException(new Swift_TransportException('Cannot send message without a sender address'));
-        }
-
-        $to = (array) $message->getTo();
-        $cc = (array) $message->getCc();
-        $tos = array_merge($to, $cc);
-        $bcc = (array) $message->getBcc();
-
-        $message->setBcc([]);
-
-        // This allows us to have the To: header set as the alias whilst still delivering to the correct RCPT TO.
-        if ($aliasTo = $message->getHeaders()->get('Alias-To')) {
-            $message->setTo($aliasTo->getFieldBodyModel());
-            $message->getHeaders()->remove('Alias-To');
-        }
-
-        try {
-            $sent += $this->sendTo($message, $reversePath, $tos, $failedRecipients);
-            $sent += $this->sendBcc($message, $reversePath, $bcc, $failedRecipients);
-        } finally {
-            $message->setBcc($bcc);
-        }
-
-        if ($evt) {
-            if ($sent == \count($to) + \count($cc) + \count($bcc)) {
-                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
-            } elseif ($sent > 0) {
-                $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
-            } else {
-                $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
-            }
-            $evt->setFailedRecipients($failedRecipients);
-            $this->eventDispatcher->dispatchEvent($evt, 'sendPerformed');
-        }
-
-        $message->generateId(); //Make sure a new Message ID is used
-
-        return $sent;
-    }
-
-    /** Send a message to the given To: recipients */
-    private function sendTo(Swift_Mime_SimpleMessage $message, $reversePath, array $to, array &$failedRecipients)
-    {
-        if (empty($to)) {
-            return 0;
-        }
-
-        return $this->doMailTransaction(
-            $message,
-            $reversePath,
-            array_keys($to),
-            $failedRecipients
-        );
-    }
-
-    /** Send a message to all Bcc: recipients */
-    private function sendBcc(Swift_Mime_SimpleMessage $message, $reversePath, array $bcc, array &$failedRecipients)
-    {
-        $sent = 0;
-        foreach ($bcc as $forwardPath => $name) {
-            $message->setBcc([$forwardPath => $name]);
-            $sent += $this->doMailTransaction(
-                $message,
-                $reversePath,
-                [$forwardPath],
-                $failedRecipients
-            );
-        }
-
-        return $sent;
-    }
-
-    /** Send an email to the given recipients from the given reverse path */
-    private function doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients)
-    {
-        $sent = 0;
-        $this->doMailFromCommand($reversePath);
-        foreach ($recipients as $forwardPath) {
-            try {
-                $this->doRcptToCommand($forwardPath);
-                ++$sent;
-            } catch (Swift_TransportException $e) {
-                $failedRecipients[] = $forwardPath;
-            } catch (Swift_AddressEncoderException $e) {
-                $failedRecipients[] = $forwardPath;
-            }
-        }
-
-        if (0 != $sent) {
-            $sent += \count($failedRecipients);
-            $this->doDataCommand($failedRecipients);
-            $sent -= \count($failedRecipients);
-
-            $this->streamMessage($message);
-        } else {
-            $this->reset();
-        }
-
-        return $sent;
-    }
-}

+ 0 - 195
app/CustomMailDriver/CustomSmtpTransport.php

@@ -1,195 +0,0 @@
-<?php
-
-namespace App\CustomMailDriver;
-
-use App\Models\PostfixQueueId;
-use Illuminate\Database\QueryException;
-use Illuminate\Support\Facades\Mail;
-use Illuminate\Support\Str;
-use Swift_AddressEncoderException;
-use Swift_DependencyContainer;
-use Swift_Events_SendEvent;
-use Swift_Mime_SimpleMessage;
-use Swift_Plugins_LoggerPlugin;
-use Swift_Plugins_Loggers_ArrayLogger;
-use Swift_Transport_EsmtpTransport;
-use Swift_TransportException;
-
-class CustomSmtpTransport extends Swift_Transport_EsmtpTransport
-{
-    /**
-     * @param string $host
-     * @param int    $port
-     * @param string|null $encryption SMTP encryption mode:
-     *        - null for plain SMTP (no encryption),
-     *        - 'tls' for SMTP with STARTTLS (best effort encryption),
-     *        - 'ssl' for SMTPS = SMTP over TLS (always encrypted).
-     */
-    public function __construct($host = 'localhost', $port = 25, $encryption = null)
-    {
-        \call_user_func_array(
-            [$this, 'Swift_Transport_EsmtpTransport::__construct'],
-            Swift_DependencyContainer::getInstance()
-                ->createDependenciesFor('transport.smtp')
-        );
-
-        $this->setHost($host);
-        $this->setPort($port);
-        $this->setEncryption($encryption);
-    }
-
-    /**
-     * Send the given Message.
-     *
-     * Recipient/sender data will be retrieved from the Message API.
-     * The return value is the number of recipients who were accepted for delivery.
-     *
-     * @param string[] $failedRecipients An array of failures by-reference
-     *
-     * @return int
-     */
-    public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
-    {
-        if (!$this->isStarted()) {
-            $this->start();
-        }
-
-        $logger = new Swift_Plugins_Loggers_ArrayLogger();
-        Mail::getSwiftMailer()->registerPlugin(new Swift_Plugins_LoggerPlugin($logger));
-
-        $sent = 0;
-        $failedRecipients = (array) $failedRecipients;
-
-        if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) {
-            $this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
-            if ($evt->bubbleCancelled()) {
-                return 0;
-            }
-        }
-
-        if (!$reversePath = $this->getReversePath($message)) {
-            $this->throwException(new Swift_TransportException('Cannot send message without a sender address'));
-        }
-
-        $to = (array) $message->getTo();
-        $cc = (array) $message->getCc();
-        $tos = array_merge($to, $cc);
-        $bcc = (array) $message->getBcc();
-
-        $message->setBcc([]);
-
-        // This allows us to have the To: header set as the alias whilst still delivering to the correct RCPT TO.
-        if ($aliasTo = $message->getHeaders()->get('Alias-To')) {
-            $message->setTo($aliasTo->getFieldBodyModel());
-            $message->getHeaders()->remove('Alias-To');
-        }
-
-        // Update Content IDs for inline image attachments
-        if ($oldCids = $message->getHeaders()->get('X-Old-Cids')) {
-            $oldCidsArray = explode(',', $oldCids->getFieldBodyModel());
-
-            $newCids = $message->getHeaders()->get('X-New-Cids');
-            $newCidsArray = explode(',', $newCids->getFieldBodyModel());
-
-            $message->getHeaders()->remove('X-Old-Cids');
-            $message->getHeaders()->remove('X-New-Cids');
-
-            $message->setBody(str_replace($oldCidsArray, $newCidsArray, $message->getBody()));
-        }
-
-        try {
-            $sent += $this->sendTo($message, $reversePath, $tos, $failedRecipients);
-            $sent += $this->sendBcc($message, $reversePath, $bcc, $failedRecipients);
-        } finally {
-            $message->setBcc($bcc);
-        }
-
-        if ($evt) {
-            if ($sent == \count($to) + \count($cc) + \count($bcc)) {
-                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
-            } elseif ($sent > 0) {
-                $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
-            } else {
-                $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
-            }
-            $evt->setFailedRecipients($failedRecipients);
-            $this->eventDispatcher->dispatchEvent($evt, 'sendPerformed');
-        }
-
-        $message->generateId(); //Make sure a new Message ID is used
-
-        try {
-            // Get Postfix Queue ID and store in the database
-            $id = str_replace("\r\n", "", Str::after($logger->dump(), 'Ok: queued as '));
-
-            PostfixQueueId::create([
-                'queue_id' => $id
-            ]);
-        } catch (QueryException $e) {
-            // duplicate entry
-        }
-
-        return $sent;
-    }
-
-    /** Send a message to the given To: recipients */
-    private function sendTo(Swift_Mime_SimpleMessage $message, $reversePath, array $to, array &$failedRecipients)
-    {
-        if (empty($to)) {
-            return 0;
-        }
-
-        return $this->doMailTransaction(
-            $message,
-            $reversePath,
-            array_keys($to),
-            $failedRecipients
-        );
-    }
-
-    /** Send a message to all Bcc: recipients */
-    private function sendBcc(Swift_Mime_SimpleMessage $message, $reversePath, array $bcc, array &$failedRecipients)
-    {
-        $sent = 0;
-        foreach ($bcc as $forwardPath => $name) {
-            $message->setBcc([$forwardPath => $name]);
-            $sent += $this->doMailTransaction(
-                $message,
-                $reversePath,
-                [$forwardPath],
-                $failedRecipients
-            );
-        }
-
-        return $sent;
-    }
-
-    /** Send an email to the given recipients from the given reverse path */
-    private function doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients)
-    {
-        $sent = 0;
-        $this->doMailFromCommand($reversePath);
-        foreach ($recipients as $forwardPath) {
-            try {
-                $this->doRcptToCommand($forwardPath);
-                ++$sent;
-            } catch (Swift_TransportException $e) {
-                $failedRecipients[] = $forwardPath;
-            } catch (Swift_AddressEncoderException $e) {
-                $failedRecipients[] = $forwardPath;
-            }
-        }
-
-        if (0 != $sent) {
-            $sent += \count($failedRecipients);
-            $this->doDataCommand($failedRecipients);
-            $sent -= \count($failedRecipients);
-
-            $this->streamMessage($message);
-        } else {
-            $this->reset();
-        }
-
-        return $sent;
-    }
-}

+ 40 - 0
app/CustomMailDriver/Mime/Crypto/AlreadyEncrypted.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\CustomMailDriver\Mime\Crypto;
+
+use App\CustomMailDriver\Mime\Part\EncryptedPart;
+use Symfony\Component\Mime\Email;
+
+class AlreadyEncrypted
+{
+    protected $encryptedParts;
+
+    public function __construct($encryptedParts)
+    {
+        $this->encryptedParts = $encryptedParts;
+    }
+
+    public function update(Email $message): Email
+    {
+        $boundary = strtr(base64_encode(random_bytes(6)), '+/', '-_');
+
+        $headers = $message->getPreparedHeaders();
+
+        $headers->setHeaderBody('Parameterized', 'Content-Type', 'multipart/encrypted');
+        $headers->setHeaderParameter('Content-Type', 'protocol', 'application/pgp-encrypted');
+        $headers->setHeaderParameter('Content-Type', 'boundary', $boundary);
+
+        $message->setHeaders($headers);
+
+        $body = "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\r\n\r\n";
+
+        foreach ($this->encryptedParts as $part) {
+            $body .= "--{$boundary}\r\n";
+            $body .= $part->getMimePartStr()."\r\n";
+        }
+
+        $body .= "--{$boundary}--";
+
+        return $message->setBody(new EncryptedPart($body));
+    }
+}

+ 302 - 0
app/CustomMailDriver/Mime/Crypto/OpenPGPEncrypter.php

@@ -0,0 +1,302 @@
+<?php
+
+namespace App\CustomMailDriver\Mime\Crypto;
+
+use App\CustomMailDriver\Mime\Part\EncryptedPart;
+use Symfony\Component\Mailer\Exception\RuntimeException;
+use Symfony\Component\Mime\Email;
+
+class OpenPGPEncrypter
+{
+    protected $gnupg = null;
+
+    /**
+     * The signing hash algorithm. 'MD5', SHA1, or SHA256. SHA256 (the default) is highly recommended
+     * unless you need to deal with an old client that doesn't support it. SHA1 and MD5 are
+     * currently considered cryptographically weak.
+     *
+     * This is apparently not supported by the PHP GnuPG module.
+     *
+     * @type string
+     */
+    protected $micalg = 'SHA256';
+
+    protected $recipientKey = null;
+
+    /**
+     * The fingerprint of the key that will be used to sign the email. Populated either with
+     * autoAddSignature or addSignature.
+     *
+     * @type string
+     */
+    protected $signingKey;
+
+    /**
+     * An associative array of keyFingerprint=>passwords to decrypt secret keys (if needed).
+     * Populated by calling addKeyPassphrase. Pointless at the moment because the GnuPG module in
+     * PHP doesn't support decrypting keys with passwords. The command line client does, so this
+     * method stays for now.
+     *
+     * @type array
+     */
+    protected $keyPassphrases = [];
+
+    /**
+     * Specifies the home directory for the GnuPG keyrings. By default this is the user's home
+     * directory + /.gnupg, however when running on a web server (eg: Apache) the home directory
+     * will likely not exist and/or not be writable. Set this by calling setGPGHome before calling
+     * any other encryption/signing methods.
+     *
+     * @var string
+     */
+    protected $gnupgHome = null;
+
+
+    public function __construct($signingKey = null, $recipientKey = null, $gnupgHome = null)
+    {
+        $this->initGNUPG();
+        $this->signingKey    = $signingKey;
+        $this->recipientKey = $recipientKey;
+        $this->gnupgHome     = $gnupgHome;
+    }
+
+    /**
+     * @param string $micalg
+     */
+    public function setMicalg($micalg)
+    {
+        $this->micalg = $micalg;
+    }
+
+    /**
+     * @param $identifier
+     * @param null $passPhrase
+     *
+     * @throws RuntimeException
+     */
+    public function addSignature($identifier, $keyFingerprint = null, $passPhrase = null)
+    {
+        if (!$keyFingerprint) {
+            $keyFingerprint   = $this->getKey($identifier, 'sign');
+        }
+        $this->signingKey = $keyFingerprint;
+
+        if ($passPhrase) {
+            $this->addKeyPassphrase($keyFingerprint, $passPhrase);
+        }
+    }
+
+    /**
+     * @param $identifier
+     * @param $passPhrase
+     *
+     * @throws RuntimeException
+     */
+    public function addKeyPassphrase($identifier, $passPhrase)
+    {
+        $keyFingerprint                        = $this->getKey($identifier, 'sign');
+        $this->keyPassphrases[$keyFingerprint] = $passPhrase;
+    }
+
+    /**
+     * @param Email $email
+     *
+     * @return $this
+     *
+     * @throws RuntimeException
+     */
+    public function encrypt(Email $message): Email
+    {
+        $originalMessage = $message->toString();
+
+        $headers = $message->getPreparedHeaders();
+
+        $boundary = strtr(base64_encode(random_bytes(6)), '+/', '-_');
+
+        $headers->setHeaderBody('Parameterized', 'Content-Type', 'multipart/signed');
+        $headers->setHeaderParameter('Content-Type', 'micalg', sprintf("pgp-%s", strtolower($this->micalg)));
+        $headers->setHeaderParameter('Content-Type', 'protocol', 'application/pgp-signature');
+        $headers->setHeaderParameter('Content-Type', 'boundary', $boundary);
+
+        $message->setHeaders($headers);
+
+        if (!$this->signingKey) {
+            foreach ($message->getFrom() as $key => $value) {
+                $this->addSignature($this->getKey($key, 'sign'));
+            }
+        }
+
+        if (!$this->signingKey) {
+            throw new RuntimeException('Signing has been enabled, but no signature has been added. Use autoAddSignature() or addSignature()');
+        }
+
+        $lines = preg_split('/(\r\n|\r|\n)/', rtrim($originalMessage));
+
+        for ($i=0; $i<count($lines); $i++) {
+            $lines[$i] = rtrim($lines[$i])."\r\n";
+        }
+
+        // Remove excess trailing newlines (RFC3156 section 5.4)
+        $signedBody = rtrim(implode('', $lines))."\r\n";
+
+        $signature = $this->pgpSignString($signedBody, $this->signingKey);
+
+        // Fixes DKIM signature incorrect body hash for custom domains
+        $body = "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n\r\n";
+        $body .= "--{$boundary}\r\n";
+        $body .= $signedBody."\r\n";
+        $body .= "--{$boundary}\r\n";
+        $body .= "Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n";
+        $body .= "Content-Description: OpenPGP digital signature\r\n";
+        $body .= "Content-Disposition: attachment; filename=\"signature.asc\"\r\n\r\n";
+        $body .= $signature."\r\n\r\n";
+        $body .= "--{$boundary}--";
+
+        $signed = sprintf("%s\r\n%s", $message->getHeaders()->get('content-type')->toString(), $body);
+
+        if (!$this->recipientKey) {
+            throw new RuntimeException('Encryption has been enabled, but no recipients have been added. Use autoAddRecipients() or addRecipient()');
+        }
+
+        //Create body from signed message
+        $encryptedBody = $this->pgpEncryptString($signed, $this->recipientKey);
+
+        $headers->setHeaderBody('Parameterized', 'Content-Type', 'multipart/encrypted');
+        $headers->setHeaderParameter('Content-Type', 'protocol', 'application/pgp-encrypted');
+        $headers->setHeaderParameter('Content-Type', 'boundary', $boundary);
+
+        // Fixes DKIM signature incorrect body hash for custom domains
+        $body = "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\r\n\r\n";
+        $body .= "--{$boundary}\r\n";
+        $body .= "Content-Type: application/pgp-encrypted\r\n";
+        $body .= "Content-Description: PGP/MIME version identification\r\n\r\n";
+        $body .= "Version: 1\r\n\r\n";
+        $body .= "--{$boundary}\r\n";
+        $body .= "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n";
+        $body .= "Content-Description: OpenPGP encrypted message\r\n";
+        $body .= "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n\r\n";
+        $body .= $encryptedBody."\r\n\r\n";
+        $body .= "--{$boundary}--";
+
+        return $message->setBody(new EncryptedPart($body));
+    }
+
+    /**
+     * @throws RuntimeException
+     */
+    protected function initGNUPG()
+    {
+        if (!class_exists('gnupg')) {
+            throw new RuntimeException('PHPMailerPGP requires the GnuPG class');
+        }
+
+        if (!$this->gnupgHome && isset($_SERVER['HOME'])) {
+            $this->gnupgHome = $_SERVER['HOME'] . '/.gnupg';
+        }
+
+        if (!$this->gnupgHome && getenv('HOME')) {
+            $this->gnupgHome = getenv('HOME') . '/.gnupg';
+        }
+
+        if (!$this->gnupg) {
+            $this->gnupg = new \gnupg();
+        }
+
+        $this->gnupg->seterrormode(\gnupg::ERROR_EXCEPTION);
+    }
+
+    /**
+     * @param $plaintext
+     * @param $keyFingerprint
+     *
+     * @return string
+     *
+     * @throws RuntimeException
+     */
+    protected function pgpSignString($plaintext, $keyFingerprint)
+    {
+        if (isset($this->keyPassphrases[$keyFingerprint]) && !$this->keyPassphrases[$keyFingerprint]) {
+            $passPhrase = $this->keyPassphrases[$keyFingerprint];
+        } else {
+            $passPhrase = null;
+        }
+
+        $this->gnupg->clearsignkeys();
+        $this->gnupg->addsignkey($keyFingerprint, $passPhrase);
+        $this->gnupg->setsignmode(\gnupg::SIG_MODE_DETACH);
+        $this->gnupg->setarmor(1);
+
+        $signed = $this->gnupg->sign($plaintext);
+
+        if ($signed) {
+            return $signed;
+        }
+
+        throw new RuntimeException('Unable to sign message (perhaps the secret key is encrypted with a passphrase?)');
+    }
+
+    /**
+     * @param $plaintext
+     * @param $keyFingerprints
+     *
+     * @return string
+     *
+     * @throws RuntimeException
+     */
+    protected function pgpEncryptString($plaintext, $keyFingerprint)
+    {
+        $this->gnupg->clearencryptkeys();
+
+        $this->gnupg->addencryptkey($keyFingerprint);
+
+        $this->gnupg->setarmor(1);
+
+        $encrypted = $this->gnupg->encrypt($plaintext);
+
+        if ($encrypted) {
+            return $encrypted;
+        }
+
+        throw new RuntimeException('Unable to encrypt message');
+    }
+
+    /**
+     * @param $identifier
+     * @param $purpose
+     *
+     * @return string
+     *
+     * @throws RuntimeException
+     */
+    protected function getKey($identifier, $purpose)
+    {
+        $keys         = $this->gnupg->keyinfo($identifier);
+        $fingerprints = [];
+
+        foreach ($keys as $key) {
+            if ($this->isValidKey($key, $purpose)) {
+                foreach ($key['subkeys'] as $subKey) {
+                    if ($this->isValidKey($subKey, $purpose)) {
+                        $fingerprints[] = $subKey['fingerprint'];
+                    }
+                }
+            }
+        }
+
+        // Return first available to encrypt
+        if (count($fingerprints) >= 1) {
+            return $fingerprints[0];
+        }
+
+        /* if (count($fingerprints) > 1) {
+            throw new Swift_SwiftException(sprintf('Found more than one active key for %s use addRecipient() or addSignature()', $identifier));
+        } */
+
+        throw new RuntimeException(sprintf('Unable to find an active key to %s for %s,try importing keys first', $purpose, $identifier));
+    }
+
+    protected function isValidKey($key, $purpose)
+    {
+        return !($key['disabled'] || $key['expired'] || $key['revoked'] || ($purpose == 'sign' && !$key['can_sign']) || ($purpose == 'encrypt' && !$key['can_encrypt']));
+    }
+}

+ 25 - 0
app/CustomMailDriver/Mime/Encoder/RawContentEncoder.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\CustomMailDriver\Mime\Encoder;
+
+use Symfony\Component\Mime\Encoder\ContentEncoderInterface;
+
+final class RawContentEncoder implements ContentEncoderInterface
+{
+    public function encodeByteStream($stream, int $maxLineLength = 0): iterable
+    {
+        while (!feof($stream)) {
+            yield fread($stream, 8192);
+        }
+    }
+
+    public function getName(): string
+    {
+        return 'raw';
+    }
+
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        return $string;
+    }
+}

+ 125 - 0
app/CustomMailDriver/Mime/Part/EncryptedPart.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace App\CustomMailDriver\Mime\Part;
+
+use App\CustomMailDriver\Mime\Encoder\RawContentEncoder;
+use Symfony\Component\Mime\Encoder\ContentEncoderInterface;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Part\AbstractPart;
+
+class EncryptedPart extends AbstractPart
+{
+    /** @internal */
+    protected $_headers;
+
+    private $body;
+    private $charset;
+    private $subtype;
+    /**
+     * @var ?string
+     */
+    private $disposition;
+    private $seekable;
+
+    /**
+     * @param resource|string $body
+     */
+    public function __construct($body, ?string $charset = 'utf-8', string $subtype = 'plain')
+    {
+        unset($this->_headers);
+
+        parent::__construct();
+
+        if (!\is_string($body) && !\is_resource($body)) {
+            throw new \TypeError(sprintf('The body of "%s" must be a string or a resource (got "%s").', self::class, get_debug_type($body)));
+        }
+
+        $this->body = $body;
+        $this->charset = $charset;
+        $this->subtype = $subtype;
+        $this->seekable = \is_resource($body) ? stream_get_meta_data($body)['seekable'] && 0 === fseek($body, 0, \SEEK_CUR) : null;
+    }
+
+    public function getMediaType(): string
+    {
+        return 'text';
+    }
+
+    public function getMediaSubtype(): string
+    {
+        return $this->subtype;
+    }
+
+    public function getBody(): string
+    {
+        if (null === $this->seekable) {
+            return $this->body;
+        }
+
+        if ($this->seekable) {
+            rewind($this->body);
+        }
+
+        return stream_get_contents($this->body) ?: '';
+    }
+
+    public function bodyToString(): string
+    {
+        return $this->getEncoder()->encodeString($this->getBody(), $this->charset);
+    }
+
+    public function bodyToIterable(): iterable
+    {
+        if (null !== $this->seekable) {
+            if ($this->seekable) {
+                rewind($this->body);
+            }
+            yield from $this->getEncoder()->encodeByteStream($this->body);
+        } else {
+            yield $this->getEncoder()->encodeString($this->body);
+        }
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        return clone new Headers();
+    }
+
+    public function asDebugString(): string
+    {
+        $str = parent::asDebugString();
+        if (null !== $this->charset) {
+            $str .= ' charset: '.$this->charset;
+        }
+        if (null !== $this->disposition) {
+            $str .= ' disposition: '.$this->disposition;
+        }
+
+        return $str;
+    }
+
+    private function getEncoder(): ContentEncoderInterface
+    {
+        return new RawContentEncoder();
+    }
+
+    public function __sleep(): array
+    {
+        // convert resources to strings for serialization
+        if (null !== $this->seekable) {
+            $this->body = $this->getBody();
+        }
+
+        $this->_headers = $this->getHeaders();
+
+        return ['_headers', 'body', 'charset', 'subtype', 'disposition', 'name', 'encoding'];
+    }
+
+    public function __wakeup()
+    {
+        $r = new \ReflectionProperty(AbstractPart::class, 'headers');
+        $r->setAccessible(true);
+        $r->setValue($this, $this->_headers);
+        unset($this->_headers);
+    }
+}

+ 48 - 0
app/CustomMailDriver/Mime/Part/InlineImagePart.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\CustomMailDriver\Mime\Part;
+
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Part\DataPart;
+
+class InlineImagePart extends DataPart
+{
+    /**
+     * Sets the content-id of the file.
+     *
+     * @return $this
+     */
+    public function setContentId(string $cid): static
+    {
+        $this->cid = $cid;
+
+        return $this;
+    }
+
+    /**
+     * Sets the name of the file.
+     *
+     * @return $this
+     */
+    public function setFileName(string $filename): static
+    {
+        $this->filename = $filename;
+
+        return $this;
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        $headers = parent::getPreparedHeaders();
+
+        if (null !== $this->cid) {
+            $headers->setHeaderBody('Id', 'Content-ID', $this->cid);
+        }
+
+        if (null !== $this->filename) {
+            $headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename);
+        }
+
+        return $headers;
+    }
+}

+ 106 - 0
app/CustomMailDriver/Mime/Part/TextPart.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace App\CustomMailDriver\Mime\Part;
+
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Part\AbstractPart;
+
+class TextPart extends AbstractPart
+{
+    /** @internal */
+    protected $_headers;
+
+    private static $encoders = [];
+
+    private $body;
+    private $boundary;
+    private $charset;
+    private $subtype;
+    /**
+     * @var ?string
+     */
+    private $disposition;
+    private $name;
+    private $encoding;
+    private $seekable;
+
+    /**
+     * @param resource|string $body
+     */
+    public function __construct($body, ?string $boundary, ?string $charset = 'utf-8', string $subtype = 'plain', string $encoding = null)
+    {
+        unset($this->_headers);
+
+        parent::__construct();
+
+        if (!\is_string($body) && !\is_resource($body)) {
+            throw new \TypeError(sprintf('The body of "%s" must be a string or a resource (got "%s").', self::class, get_debug_type($body)));
+        }
+
+        $this->body = $body;
+        $this->boundary = $boundary;
+        $this->charset = $charset;
+        $this->subtype = $subtype;
+        $this->seekable = \is_resource($body) ? stream_get_meta_data($body)['seekable'] && 0 === fseek($body, 0, \SEEK_CUR) : null;
+
+        if (null === $encoding) {
+            $this->encoding = $this->chooseEncoding();
+        } else {
+            if ('quoted-printable' !== $encoding && 'base64' !== $encoding && '8bit' !== $encoding) {
+                throw new InvalidArgumentException(sprintf('The encoding must be one of "quoted-printable", "base64", or "8bit" ("%s" given).', $encoding));
+            }
+            $this->encoding = $encoding;
+        }
+    }
+
+    public function getMediaType(): string
+    {
+        return 'text';
+    }
+
+    public function getMediaSubtype(): string
+    {
+        return $this->subtype;
+    }
+
+    public function bodyToString(): string
+    {
+        return $this->getEncoder()->encodeString($this->getBody(), $this->charset);
+    }
+
+    public function bodyToIterable(): iterable
+    {
+        if (null !== $this->seekable) {
+            if ($this->seekable) {
+                rewind($this->body);
+            }
+            yield from $this->getEncoder()->encodeByteStream($this->body);
+        } else {
+            yield $this->getEncoder()->encodeString($this->body);
+        }
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        $headers = parent::getPreparedHeaders();
+
+        $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype());
+        if ($this->charset) {
+            $headers->setHeaderParameter('Content-Type', 'charset', $this->charset);
+        }
+        if ($this->name && 'form-data' !== $this->disposition) {
+            $headers->setHeaderParameter('Content-Type', 'name', $this->name);
+        }
+        $headers->setHeaderBody('Text', 'Content-Transfer-Encoding', $this->encoding);
+
+        if (!$headers->has('Content-Disposition') && null !== $this->disposition) {
+            $headers->setHeaderBody('Parameterized', 'Content-Disposition', $this->disposition);
+            if ($this->name) {
+                $headers->setHeaderParameter('Content-Disposition', 'name', $this->name);
+            }
+        }
+
+        return $headers;
+    }
+}

+ 13 - 0
app/Exceptions/CouldNotGetVersionException.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Exceptions;
+
+use RuntimeException;
+
+class CouldNotGetVersionException extends RuntimeException
+{
+    public function __construct()
+    {
+        parent::__construct("Could not get version string (`git describe` failed)");
+    }
+}

+ 0 - 74
app/Helpers/AlreadyEncryptedSigner.php

@@ -1,74 +0,0 @@
-<?php
-
-namespace App\Helpers;
-
-use Swift_DependencyContainer;
-use Swift_Message;
-use Swift_Signers_BodySigner;
-use Swift_SwiftException;
-
-class AlreadyEncryptedSigner implements Swift_Signers_BodySigner
-{
-    protected $attachments;
-
-    public function __construct($attachments)
-    {
-        $this->attachments = $attachments;
-    }
-
-    /**
-     * @param Swift_Message $message
-     *
-     * @return $this
-     *
-     * @throws Swift_DependencyException
-     * @throws Swift_SwiftException
-     */
-    public function signMessage(Swift_Message $message)
-    {
-        $message->setChildren([]);
-
-        $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder'));
-
-        $type = $message->getHeaders()->get('Content-Type');
-
-        $type->setValue('multipart/encrypted');
-
-        $type->setParameters([
-            'protocol' => 'application/pgp-encrypted',
-            'boundary' => $message->getBoundary()
-        ]);
-
-        $body = 'This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)' . PHP_EOL;
-
-        foreach ($this->attachments as $attachment) {
-            $body .= '--' . $message->getBoundary() . PHP_EOL;
-            $body .= $attachment->getMimePartStr() . PHP_EOL;
-        }
-
-        $body .= '--'. $message->getBoundary() . '--';
-
-        $message->setBody($body);
-
-        $messageHeaders = $message->getHeaders();
-        $messageHeaders->removeAll('Content-Transfer-Encoding');
-
-        return $this;
-    }
-
-    /**
-     * @return array
-     */
-    public function getAlteredHeaders()
-    {
-        return ['Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition', 'Content-Description'];
-    }
-
-    /**
-     * @return $this
-     */
-    public function reset()
-    {
-        return $this;
-    }
-}

+ 71 - 0
app/Helpers/GitVersionHelper.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Helpers;
+
+use App\Exceptions\CouldNotGetVersionException;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Str;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+class GitVersionHelper
+{
+    public static function version()
+    {
+        if (Cache::has('app-version')) {
+            return Cache::get('app-version');
+        }
+
+        return self::cacheFreshVersion();
+    }
+
+    public static function cacheFreshVersion()
+    {
+        $version = self::freshVersion();
+        Cache::put('app-version', $version);
+
+        return $version;
+    }
+
+    public static function freshVersion()
+    {
+        $path = base_path();
+
+        // Get version string from git
+        $command = 'git describe --tags $(git rev-list --tags --max-count=1)';
+        $fail = false;
+        if (class_exists('\Symfony\Component\Process\Process')) {
+            try {
+                if (method_exists(Process::class, 'fromShellCommandline')) {
+                    $process = Process::fromShellCommandline($command, $path);
+                } else {
+                    $process = new Process([$command], $path);
+                }
+
+                $process->mustRun();
+                $output = $process->getOutput();
+            } catch (RuntimeException $e) {
+                $fail = true;
+            }
+        } else {
+            // Remember current directory
+            $dir = getcwd();
+
+            // Change to base directory
+            chdir($path);
+
+            $output = shell_exec($command);
+
+            // Change back
+            chdir($dir);
+
+            $fail = $output === null;
+        }
+
+        if ($fail) {
+            throw new CouldNotGetVersionException();
+        }
+
+        return Str::of($output)->after('v')->trim();
+    }
+}

+ 0 - 433
app/Helpers/OpenPGPSigner.php

@@ -1,433 +0,0 @@
-<?php
-
-namespace App\Helpers;
-
-use Swift_DependencyContainer;
-use Swift_Message;
-use Swift_Signers_BodySigner;
-use Swift_SwiftException;
-
-/*
- * This file is part of SwiftMailer.
- * (c) 2004-2009 Chris Corbyn
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-/**
- * Message Signer used to apply OpenPGP Signature/Encryption to a message.
- *
- * @author Artem Zhuravlev <infzanoza@gmail.com>
- */
-class OpenPGPSigner implements Swift_Signers_BodySigner
-{
-    protected $gnupg = null;
-
-    /**
-     * The signing hash algorithm. 'MD5', SHA1, or SHA256. SHA256 (the default) is highly recommended
-     * unless you need to deal with an old client that doesn't support it. SHA1 and MD5 are
-     * currently considered cryptographically weak.
-     *
-     * This is apparently not supported by the PHP GnuPG module.
-     *
-     * @type string
-     */
-    protected $micalg = 'SHA256';
-
-    /**
-     * An associative array of identifier=>keyFingerprint for the recipients we'll encrypt the email
-     * to, where identifier is usually the email address, but could be anything used to look up a
-     * key (including the fingerprint itself). This is populated either by autoAddRecipients or by
-     * calling addRecipient.
-     *
-     * @type array
-     */
-    protected $recipientKeys = [];
-
-    /**
-     * The fingerprint of the key that will be used to sign the email. Populated either with
-     * autoAddSignature or addSignature.
-     *
-     * @type string
-     */
-    protected $signingKey;
-
-    /**
-     * An associative array of keyFingerprint=>passwords to decrypt secret keys (if needed).
-     * Populated by calling addKeyPassphrase. Pointless at the moment because the GnuPG module in
-     * PHP doesn't support decrypting keys with passwords. The command line client does, so this
-     * method stays for now.
-     *
-     * @type array
-     */
-    protected $keyPassphrases = [];
-
-    /**
-     * Specifies the home directory for the GnuPG keyrings. By default this is the user's home
-     * directory + /.gnupg, however when running on a web server (eg: Apache) the home directory
-     * will likely not exist and/or not be writable. Set this by calling setGPGHome before calling
-     * any other encryption/signing methods.
-     *
-     * @var string
-     */
-    protected $gnupgHome = null;
-
-    /**
-     * @var bool
-     */
-    protected $encrypt = true;
-
-
-    public function __construct($signingKey = null, $recipientKeys = [], $gnupgHome = null)
-    {
-        $this->initGNUPG();
-        $this->signingKey    = $signingKey;
-        $this->recipientKeys = $recipientKeys;
-        $this->gnupgHome     = $gnupgHome;
-    }
-
-    public static function newInstance($signingKey = null, $recipientKeys = [], $gnupgHome = null)
-    {
-        return new self($signingKey, $recipientKeys, $gnupgHome);
-    }
-
-    /**
-     * @param boolean $encrypt
-     */
-    public function setEncrypt($encrypt)
-    {
-        $this->encrypt = $encrypt;
-    }
-
-    /**
-     * @param string $gnupgHome
-     */
-    public function setGnupgHome($gnupgHome)
-    {
-        $this->gnupgHome = $gnupgHome;
-    }
-
-    /**
-     * @param string $micalg
-     */
-    public function setMicalg($micalg)
-    {
-        $this->micalg = $micalg;
-    }
-
-    /**
-     * @param $identifier
-     * @param null $passPhrase
-     *
-     * @throws Swift_SwiftException
-     */
-    public function addSignature($identifier, $keyFingerprint = null, $passPhrase = null)
-    {
-        if (!$keyFingerprint) {
-            $keyFingerprint   = $this->getKey($identifier, 'sign');
-        }
-        $this->signingKey = $keyFingerprint;
-
-        if ($passPhrase) {
-            $this->addKeyPassphrase($keyFingerprint, $passPhrase);
-        }
-    }
-
-    /**
-     * @param $identifier
-     * @param $passPhrase
-     *
-     * @throws Swift_SwiftException
-     */
-    public function addKeyPassphrase($identifier, $passPhrase)
-    {
-        $keyFingerprint                        = $this->getKey($identifier, 'sign');
-        $this->keyPassphrases[$keyFingerprint] = $passPhrase;
-    }
-
-    /**
-     * Adds a recipient to encrypt a copy of the email for. If you exclude a key fingerprint, we
-     * will try to find a matching key based on the identifier. However if no match is found, or
-     * if multiple valid keys are found, this will fail. Specifying a key fingerprint avoids these
-     * issues.
-     *
-     * @param string $identifier
-     * an email address, but could be a key fingerprint, key ID, name, etc.
-     *
-     * @param string $keyFingerprint
-     */
-    public function addRecipient($identifier, $keyFingerprint = null)
-    {
-        if (!$keyFingerprint) {
-            $keyFingerprint = $this->getKey($identifier, 'encrypt');
-        }
-
-        $this->recipientKeys[$identifier] = $keyFingerprint;
-    }
-
-    /**
-     * @param Swift_Message $message
-     *
-     * @return $this
-     *
-     * @throws Swift_DependencyException
-     * @throws Swift_SwiftException
-     */
-    public function signMessage(Swift_Message $message)
-    {
-        $originalMessage = $this->createMessage($message);
-
-        $message->setChildren([]);
-
-        $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder'));
-
-        $type = $message->getHeaders()->get('Content-Type');
-
-        $type->setValue('multipart/signed');
-
-        $type->setParameters([
-            'micalg'   => sprintf("pgp-%s", strtolower($this->micalg)),
-            'protocol' => 'application/pgp-signature',
-            'boundary' => $message->getBoundary()
-        ]);
-
-        if (!$this->signingKey) {
-            foreach ($message->getFrom() as $key => $value) {
-                $this->addSignature($this->getKey($key, 'sign'));
-            }
-        }
-
-        if (!$this->signingKey) {
-            throw new Swift_SwiftException('Signing has been enabled, but no signature has been added. Use autoAddSignature() or addSignature()');
-        }
-
-        $signedBody = $originalMessage->toString();
-
-        $lines = preg_split('/(\r\n|\r|\n)/', rtrim($signedBody));
-
-        for ($i=0; $i<count($lines); $i++) {
-            $lines[$i] = rtrim($lines[$i])."\r\n";
-        }
-
-        // Remove excess trailing newlines (RFC3156 section 5.4)
-        $signedBody = rtrim(implode('', $lines))."\r\n";
-
-        $signature = $this->pgpSignString($signedBody, $this->signingKey);
-
-        //Swiftmailer is automatically changing content type and this is the hack to prevent it
-        // Fixes DKIM signature incorrect body hash for custom domains
-        $body = "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n\r\n";
-        $body .= "--{$message->getBoundary()}\r\n";
-        $body .= $signedBody."\r\n";
-        $body .= "--{$message->getBoundary()}\r\n";
-        $body .= "Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n";
-        $body .= "Content-Description: OpenPGP digital signature\r\n";
-        $body .= "Content-Disposition: attachment; filename=\"signature.asc\"\r\n\r\n";
-        $body .= $signature."\r\n\r\n";
-        $body .= "--{$message->getBoundary()}--";
-
-        $message->setBody($body);
-
-        if ($this->encrypt) {
-            $signed = sprintf("%s\r\n%s", $message->getHeaders()->get('Content-Type')->toString(), $body);
-
-            if (!$this->recipientKeys) {
-                foreach ($message->getTo() as $key => $value) {
-                    if (!isset($this->recipientKeys[$key])) {
-                        $this->addRecipient($key);
-                    }
-                }
-            }
-
-            if (!$this->recipientKeys) {
-                throw new Swift_SwiftException('Encryption has been enabled, but no recipients have been added. Use autoAddRecipients() or addRecipient()');
-            }
-
-            //Create body from signed message
-            $encryptedBody = $this->pgpEncryptString($signed, array_keys($this->recipientKeys));
-
-            $type = $message->getHeaders()->get('Content-Type');
-
-            $type->setValue('multipart/encrypted');
-
-            $type->setParameters([
-                'protocol' => 'application/pgp-encrypted',
-                'boundary' => $message->getBoundary()
-            ]);
-
-            // Fixes DKIM signature incorrect body hash for custom domains
-            $body = "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\r\n\r\n";
-            $body .= "--{$message->getBoundary()}\r\n";
-            $body .= "Content-Type: application/pgp-encrypted\r\n";
-            $body .= "Content-Description: PGP/MIME version identification\r\n\r\n";
-            $body .= "Version: 1\r\n\r\n";
-            $body .= "--{$message->getBoundary()}\r\n";
-            $body .= "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n";
-            $body .= "Content-Description: OpenPGP encrypted message\r\n";
-            $body .= "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n\r\n";
-            $body .= $encryptedBody."\r\n\r\n";
-            $body .= "--{$message->getBoundary()}--";
-
-            $message->setBody($body);
-        }
-
-        $messageHeaders = $message->getHeaders();
-        $messageHeaders->removeAll('Content-Transfer-Encoding');
-
-        return $this;
-    }
-
-    /**
-     * @return array
-     */
-    public function getAlteredHeaders()
-    {
-        return ['Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition', 'Content-Description'];
-    }
-
-    /**
-     * @return $this
-     */
-    public function reset()
-    {
-        return $this;
-    }
-
-    protected function createMessage(Swift_Message $message)
-    {
-        $mimeEntity = new Swift_Message('', $message->getBody(), $message->getContentType(), $message->getCharset());
-        $mimeEntity->setChildren($message->getChildren());
-
-        $messageHeaders = $mimeEntity->getHeaders();
-        $messageHeaders->remove('Message-ID');
-        $messageHeaders->remove('Date');
-        $messageHeaders->remove('Subject');
-        $messageHeaders->remove('MIME-Version');
-        $messageHeaders->remove('To');
-        $messageHeaders->remove('From');
-
-        return $mimeEntity;
-    }
-
-    /**
-     * @throws Swift_SwiftException
-     */
-    protected function initGNUPG()
-    {
-        if (!class_exists('gnupg')) {
-            throw new Swift_SwiftException('PHPMailerPGP requires the GnuPG class');
-        }
-
-        if (!$this->gnupgHome && isset($_SERVER['HOME'])) {
-            $this->gnupgHome = $_SERVER['HOME'] . '/.gnupg';
-        }
-
-        if (!$this->gnupgHome && getenv('HOME')) {
-            $this->gnupgHome = getenv('HOME') . '/.gnupg';
-        }
-
-        if (!$this->gnupg) {
-            $this->gnupg = new \gnupg();
-        }
-
-        $this->gnupg->seterrormode(\gnupg::ERROR_EXCEPTION);
-    }
-
-    /**
-     * @param $plaintext
-     * @param $keyFingerprint
-     *
-     * @return string
-     *
-     * @throws Swift_SwiftException
-     */
-    protected function pgpSignString($plaintext, $keyFingerprint)
-    {
-        if (isset($this->keyPassphrases[$keyFingerprint]) && !$this->keyPassphrases[$keyFingerprint]) {
-            $passPhrase = $this->keyPassphrases[$keyFingerprint];
-        } else {
-            $passPhrase = null;
-        }
-
-        $this->gnupg->clearsignkeys();
-        $this->gnupg->addsignkey($keyFingerprint, $passPhrase);
-        $this->gnupg->setsignmode(\gnupg::SIG_MODE_DETACH);
-        $this->gnupg->setarmor(1);
-
-        $signed = $this->gnupg->sign($plaintext);
-
-        if ($signed) {
-            return $signed;
-        }
-
-        throw new Swift_SwiftException('Unable to sign message (perhaps the secret key is encrypted with a passphrase?)');
-    }
-
-    /**
-     * @param $plaintext
-     * @param $keyFingerprints
-     *
-     * @return string
-     *
-     * @throws Swift_SwiftException
-     */
-    protected function pgpEncryptString($plaintext, $keyFingerprints)
-    {
-        $this->gnupg->clearencryptkeys();
-
-        foreach ($keyFingerprints as $keyFingerprint) {
-            $this->gnupg->addencryptkey($keyFingerprint);
-        }
-
-        $this->gnupg->setarmor(1);
-
-        $encrypted = $this->gnupg->encrypt($plaintext);
-
-        if ($encrypted) {
-            return $encrypted;
-        }
-
-        throw new Swift_SwiftException('Unable to encrypt message');
-    }
-
-    /**
-     * @param $identifier
-     * @param $purpose
-     *
-     * @return string
-     *
-     * @throws Swift_SwiftException
-     */
-    protected function getKey($identifier, $purpose)
-    {
-        $keys         = $this->gnupg->keyinfo($identifier);
-        $fingerprints = [];
-
-        foreach ($keys as $key) {
-            if ($this->isValidKey($key, $purpose)) {
-                foreach ($key['subkeys'] as $subKey) {
-                    if ($this->isValidKey($subKey, $purpose)) {
-                        $fingerprints[] = $subKey['fingerprint'];
-                    }
-                }
-            }
-        }
-
-        // Return first available to encrypt
-        if (count($fingerprints) >= 1) {
-            return $fingerprints[0];
-        }
-
-        /* if (count($fingerprints) > 1) {
-            throw new Swift_SwiftException(sprintf('Found more than one active key for %s use addRecipient() or addSignature()', $identifier));
-        } */
-
-        throw new Swift_SwiftException(sprintf('Unable to find an active key to %s for %s,try importing keys first', $purpose, $identifier));
-    }
-
-    protected function isValidKey($key, $purpose)
-    {
-        return !($key['disabled'] || $key['expired'] || $key['revoked'] || ($purpose == 'sign' && !$key['can_sign']) || ($purpose == 'encrypt' && !$key['can_encrypt']));
-    }
-}

+ 1 - 3
app/Http/Controllers/AliasExportController.php

@@ -9,8 +9,6 @@ class AliasExportController extends Controller
 {
 {
     public function export()
     public function export()
     {
     {
-        //return (new AliasesExport)->download('aliases.csv', \Maatwebsite\Excel\Excel::CSV);
-
-        return Excel::download(new AliasesExport, 'aliases-'.now()->toDateString().'.csv');
+        return Excel::download(new AliasesExport(), 'aliases-'.now()->toDateString().'.csv');
     }
     }
 }
 }

+ 6 - 4
app/Http/Controllers/Api/AppVersionController.php

@@ -2,18 +2,20 @@
 
 
 namespace App\Http\Controllers\Api;
 namespace App\Http\Controllers\Api;
 
 
+use App\Helpers\GitVersionHelper as Version;
 use App\Http\Controllers\Controller;
 use App\Http\Controllers\Controller;
-use PragmaRX\Version\Package\Facade as Version;
 
 
 class AppVersionController extends Controller
 class AppVersionController extends Controller
 {
 {
     public function index()
     public function index()
     {
     {
+        $parts = str(Version::version())->explode('.');
+
         return response()->json([
         return response()->json([
             'version' => Version::version(),
             'version' => Version::version(),
-            'major' => (int) Version::major(),
-            'minor' => (int) Version::minor(),
-            'patch' => (int) Version::patch()
+            'major' => (int) $parts[0],
+            'minor' => (int) $parts[1],
+            'patch' => (int) $parts[2]
         ]);
         ]);
     }
     }
 }
 }

+ 4 - 4
app/Http/Controllers/Auth/RegisterController.php

@@ -62,16 +62,16 @@ class RegisterController extends Controller
                 'regex:/^[a-zA-Z0-9]*$/',
                 'regex:/^[a-zA-Z0-9]*$/',
                 'max:20',
                 'max:20',
                 'unique:usernames,username',
                 'unique:usernames,username',
-                new NotBlacklisted,
-                new NotDeletedUsername
+                new NotBlacklisted(),
+                new NotDeletedUsername()
             ],
             ],
             'email' => [
             'email' => [
                 'required',
                 'required',
                 'email:rfc,dns',
                 'email:rfc,dns',
                 'max:254',
                 'max:254',
                 'confirmed',
                 'confirmed',
-                new RegisterUniqueRecipient,
-                new NotLocalRecipient
+                new RegisterUniqueRecipient(),
+                new NotLocalRecipient()
             ],
             ],
             'password' => ['required', 'min:8'],
             'password' => ['required', 'min:8'],
         ], [
         ], [

+ 11 - 32
app/Http/Controllers/Auth/WebauthnController.php

@@ -2,7 +2,6 @@
 
 
 namespace App\Http\Controllers\Auth;
 namespace App\Http\Controllers\Auth;
 
 
-use App\Actions\RegisterKeyStore;
 use App\Facades\Webauthn as WebauthnFacade;
 use App\Facades\Webauthn as WebauthnFacade;
 use App\Models\WebauthnKey;
 use App\Models\WebauthnKey;
 use Illuminate\Database\Eloquent\ModelNotFoundException;
 use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -10,10 +9,11 @@ use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Redirect;
 use Illuminate\Support\Facades\Redirect;
 use Illuminate\Support\Facades\Response;
 use Illuminate\Support\Facades\Response;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
-use LaravelWebauthn\Actions\RegisterKeyPrepare;
+use LaravelWebauthn\Actions\PrepareCreationData;
+use LaravelWebauthn\Actions\ValidateKeyCreation;
+use LaravelWebauthn\Contracts\RegisterViewResponse;
 use LaravelWebauthn\Http\Controllers\WebauthnKeyController as ControllersWebauthnController;
 use LaravelWebauthn\Http\Controllers\WebauthnKeyController as ControllersWebauthnController;
-use LaravelWebauthn\Services\Webauthn;
-use Webauthn\PublicKeyCredentialCreationOptions;
+use LaravelWebauthn\Http\Requests\WebauthnRegisterRequest;
 
 
 class WebauthnController extends ControllersWebauthnController
 class WebauthnController extends ControllersWebauthnController
 {
 {
@@ -22,13 +22,6 @@ class WebauthnController extends ControllersWebauthnController
         return user()->webauthnKeys()->latest()->select(['id','name','enabled','created_at'])->get()->values();
         return user()->webauthnKeys()->latest()->select(['id','name','enabled','created_at'])->get()->values();
     }
     }
 
 
-    /**
-     * PublicKey Creation session name.
-     *
-     * @var string
-     */
-    private const SESSION_PUBLICKEY_CREATION = 'webauthn.publicKeyCreation';
-
     /**
     /**
      * Return the register data to attempt a Webauthn registration.
      * Return the register data to attempt a Webauthn registration.
      *
      *
@@ -37,49 +30,35 @@ class WebauthnController extends ControllersWebauthnController
      */
      */
     public function create(Request $request)
     public function create(Request $request)
     {
     {
-        $publicKey = $this->app[RegisterKeyPrepare::class]($request->user());
-
-        $request->session()->put(Webauthn::SESSION_PUBLICKEY_CREATION, $publicKey);
+        $publicKey = app(PrepareCreationData::class)($request->user());
 
 
-        return view('vendor.webauthn.register')->with('publicKey', $publicKey);
+        return app(RegisterViewResponse::class)
+            ->setPublicKey($request, $publicKey);
     }
     }
 
 
     /**
     /**
      * Validate and create the Webauthn request.
      * Validate and create the Webauthn request.
      *
      *
-     * @param \Illuminate\Http\Request $request
+     * @param  WebauthnRegisterRequest  $request
      * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
      * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
      */
      */
-    public function store(Request $request)
+    public function store(WebauthnRegisterRequest $request)
     {
     {
         $request->validate([
         $request->validate([
-            'register' => 'required|string',
             'name' => 'required|string|max:50'
             'name' => 'required|string|max:50'
         ]);
         ]);
 
 
         try {
         try {
-            $publicKey = $request->session()->pull(self::SESSION_PUBLICKEY_CREATION);
-            if (! $publicKey instanceof PublicKeyCredentialCreationOptions) {
-                throw new ModelNotFoundException(trans('webauthn::errors.create_data_not_found'));
-            }
-
-            /** @var \LaravelWebauthn\Models\WebauthnKey|null */
-            $webauthnKey = $this->app[RegisterKeyStore::class](
+            app(ValidateKeyCreation::class)(
                 $request->user(),
                 $request->user(),
-                $publicKey,
-                $request->input('register'),
+                $request->only(['id', 'rawId', 'response', 'type']),
                 $request->input('name')
                 $request->input('name')
             );
             );
 
 
-            if ($webauthnKey !== null) {
-                $request->session()->put(Webauthn::SESSION_WEBAUTHNID_CREATED, $webauthnKey->id);
-            }
-
             user()->update([
             user()->update([
                 'two_factor_enabled' => false
                 'two_factor_enabled' => false
             ]);
             ]);
 
 
-
             return $this->redirectAfterSuccessRegister();
             return $this->redirectAfterSuccessRegister();
         } catch (\Exception $e) {
         } catch (\Exception $e) {
             return Response::json([
             return Response::json([

+ 3 - 1
app/Http/Controllers/Controller.php

@@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController;
 
 
 class Controller extends BaseController
 class Controller extends BaseController
 {
 {
-    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
+    use AuthorizesRequests;
+    use DispatchesJobs;
+    use ValidatesRequests;
 }
 }

+ 3 - 0
app/Http/Controllers/SettingController.php

@@ -34,6 +34,9 @@ class SettingController extends Controller
 
 
         DeleteAccount::dispatch(user());
         DeleteAccount::dispatch(user());
 
 
+        auth()->logout();
+        $request->session()->invalidate();
+
         return redirect()->route('login')
         return redirect()->route('login')
         ->with(['status' => 'Account deleted successfully!']);
         ->with(['status' => 'Account deleted successfully!']);
     }
     }

+ 2 - 1
app/Http/Kernel.php

@@ -16,7 +16,7 @@ class Kernel extends HttpKernel
     protected $middleware = [
     protected $middleware = [
         // \App\Http\Middleware\TrustHosts::class,
         // \App\Http\Middleware\TrustHosts::class,
         \App\Http\Middleware\TrustProxies::class,
         \App\Http\Middleware\TrustProxies::class,
-        \Fruitcake\Cors\HandleCors::class,
+        \Illuminate\Http\Middleware\HandleCors::class,
         \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
         \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
         \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
         \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
         \App\Http\Middleware\TrimStrings::class,
         \App\Http\Middleware\TrimStrings::class,
@@ -56,6 +56,7 @@ class Kernel extends HttpKernel
     protected $routeMiddleware = [
     protected $routeMiddleware = [
         'auth' => \App\Http\Middleware\Authenticate::class,
         'auth' => \App\Http\Middleware\Authenticate::class,
         'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
         'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+        'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
         'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
         'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
         'can' => \Illuminate\Auth\Middleware\Authorize::class,
         'can' => \Illuminate\Auth\Middleware\Authorize::class,
         'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
         'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,

+ 7 - 2
app/Http/Middleware/TrustProxies.php

@@ -2,7 +2,7 @@
 
 
 namespace App\Http\Middleware;
 namespace App\Http\Middleware;
 
 
-use Fideloper\Proxy\TrustProxies as Middleware;
+use Illuminate\Http\Middleware\TrustProxies as Middleware;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
 
 
 class TrustProxies extends Middleware
 class TrustProxies extends Middleware
@@ -19,5 +19,10 @@ class TrustProxies extends Middleware
      *
      *
      * @var int
      * @var int
      */
      */
-    protected $headers = Request::HEADER_X_FORWARDED_ALL;
+    protected $headers =
+        Request::HEADER_X_FORWARDED_FOR |
+        Request::HEADER_X_FORWARDED_HOST |
+        Request::HEADER_X_FORWARDED_PORT |
+        Request::HEADER_X_FORWARDED_PROTO |
+        Request::HEADER_X_FORWARDED_AWS_ELB;
 }
 }

+ 1 - 1
app/Http/Requests/EditDefaultRecipientRequest.php

@@ -30,7 +30,7 @@ class EditDefaultRecipientRequest extends FormRequest
                 'email:rfc,dns',
                 'email:rfc,dns',
                 'max:254',
                 'max:254',
                 'confirmed',
                 'confirmed',
-                new RegisterUniqueRecipient,
+                new RegisterUniqueRecipient(),
                 'not_in:'.$this->user()->email
                 'not_in:'.$this->user()->email
             ]
             ]
         ];
         ];

+ 1 - 1
app/Http/Requests/StoreAliasRecipientRequest.php

@@ -28,7 +28,7 @@ class StoreAliasRecipientRequest extends FormRequest
             'recipient_ids' => [
             'recipient_ids' => [
                 'array',
                 'array',
                 'max:10',
                 'max:10',
-                new VerifiedRecipientId
+                new VerifiedRecipientId()
             ]
             ]
         ];
         ];
     }
     }

+ 2 - 2
app/Http/Requests/StoreAliasRequest.php

@@ -38,7 +38,7 @@ class StoreAliasRequest extends FormRequest
                 'nullable',
                 'nullable',
                 'array',
                 'array',
                 'max:10',
                 'max:10',
-                new VerifiedRecipientId
+                new VerifiedRecipientId()
             ]
             ]
         ];
         ];
     }
     }
@@ -52,7 +52,7 @@ class StoreAliasRequest extends FormRequest
                 return $query->where('local_part', $this->validationData()['local_part'])
                 return $query->where('local_part', $this->validationData()['local_part'])
                 ->where('domain', $this->validationData()['domain']);
                 ->where('domain', $this->validationData()['domain']);
             }),
             }),
-            new ValidAliasLocalPart
+            new ValidAliasLocalPart()
         ], function () {
         ], function () {
             $format = $this->validationData()['format'] ?? 'random_characters';
             $format = $this->validationData()['format'] ?? 'random_characters';
             return $format === 'custom';
             return $format === 'custom';

+ 3 - 3
app/Http/Requests/StoreDomainRequest.php

@@ -32,9 +32,9 @@ class StoreDomainRequest extends FormRequest
                 'string',
                 'string',
                 'max:50',
                 'max:50',
                 'unique:domains',
                 'unique:domains',
-                new ValidDomain,
-                new NotLocalDomain,
-                new NotUsedAsRecipientDomain
+                new ValidDomain(),
+                new NotLocalDomain(),
+                new NotUsedAsRecipientDomain()
             ]
             ]
         ];
         ];
     }
     }

+ 2 - 2
app/Http/Requests/StoreRecipientRequest.php

@@ -31,8 +31,8 @@ class StoreRecipientRequest extends FormRequest
                 'string',
                 'string',
                 'max:254',
                 'max:254',
                 'email:rfc',
                 'email:rfc',
-                new UniqueRecipient,
-                new NotLocalRecipient
+                new UniqueRecipient(),
+                new NotLocalRecipient()
             ]
             ]
         ];
         ];
     }
     }

+ 1 - 1
app/Http/Requests/StoreReorderRuleRequest.php

@@ -28,7 +28,7 @@ class StoreReorderRuleRequest extends FormRequest
             'ids' => [
             'ids' => [
                 'required',
                 'required',
                 'array',
                 'array',
-                new ValidRuleId
+                new ValidRuleId()
             ]
             ]
         ];
         ];
     }
     }

+ 2 - 2
app/Http/Requests/StoreUsernameRequest.php

@@ -31,8 +31,8 @@ class StoreUsernameRequest extends FormRequest
                 'regex:/^[a-zA-Z0-9]*$/',
                 'regex:/^[a-zA-Z0-9]*$/',
                 'max:20',
                 'max:20',
                 'unique:usernames,username',
                 'unique:usernames,username',
-                new NotBlacklisted,
-                new NotDeletedUsername
+                new NotBlacklisted(),
+                new NotDeletedUsername()
             ],
             ],
         ];
         ];
     }
     }

+ 4 - 1
app/Jobs/DeleteAccount.php

@@ -12,7 +12,10 @@ use Illuminate\Queue\SerializesModels;
 
 
 class DeleteAccount implements ShouldQueue, ShouldBeEncrypted
 class DeleteAccount implements ShouldQueue, ShouldBeEncrypted
 {
 {
-    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+    use Dispatchable;
+    use InteractsWithQueue;
+    use Queueable;
+    use SerializesModels;
 
 
     protected $user;
     protected $user;
 
 

+ 31 - 67
app/Mail/ForwardEmail.php

@@ -2,13 +2,11 @@
 
 
 namespace App\Mail;
 namespace App\Mail;
 
 
-use App\Helpers\AlreadyEncryptedSigner;
-use App\Helpers\OpenPGPSigner;
+use App\CustomMailDriver\Mime\Part\InlineImagePart;
 use App\Models\Alias;
 use App\Models\Alias;
 use App\Models\EmailData;
 use App\Models\EmailData;
 use App\Models\Recipient;
 use App\Models\Recipient;
 use App\Notifications\FailedDeliveryNotification;
 use App\Notifications\FailedDeliveryNotification;
-use App\Notifications\GpgKeyExpired;
 use App\Traits\CheckUserRules;
 use App\Traits\CheckUserRules;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -17,13 +15,13 @@ use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Support\Facades\URL;
 use Illuminate\Support\Facades\URL;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
-use Swift_Image;
-use Swift_Signers_DKIMSigner;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
 class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
 {
 {
-    use Queueable, SerializesModels, CheckUserRules;
+    use Queueable;
+    use SerializesModels;
+    use CheckUserRules;
 
 
     protected $email;
     protected $email;
     protected $user;
     protected $user;
@@ -41,8 +39,6 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
     protected $deactivateUrl;
     protected $deactivateUrl;
     protected $bannerLocation;
     protected $bannerLocation;
     protected $fingerprint;
     protected $fingerprint;
-    protected $openpgpsigner;
-    protected $dkimSigner;
     protected $encryptedParts;
     protected $encryptedParts;
     protected $fromEmail;
     protected $fromEmail;
     protected $size;
     protected $size;
@@ -90,23 +86,9 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $this->encryptedParts = $emailData->encryptedParts ?? null;
         $this->encryptedParts = $emailData->encryptedParts ?? null;
         $this->recipientId = $recipient->id;
         $this->recipientId = $recipient->id;
 
 
-        $fingerprint = $recipient->should_encrypt && !$this->isAlreadyEncrypted() ? $recipient->fingerprint : null;
+        $this->fingerprint = $recipient->should_encrypt && !$this->isAlreadyEncrypted() ? $recipient->fingerprint : null;
 
 
         $this->bannerLocation = $this->isAlreadyEncrypted() ? 'off' : $this->alias->user->banner_location;
         $this->bannerLocation = $this->isAlreadyEncrypted() ? 'off' : $this->alias->user->banner_location;
-
-        if ($this->fingerprint = $fingerprint) {
-            try {
-                $this->openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $this->openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $this->openpgpsigner = null;
-
-                $recipient->update(['should_encrypt' => false]);
-
-                $recipient->notify(new GpgKeyExpired);
-            }
-        }
     }
     }
 
 
     /**
     /**
@@ -128,19 +110,7 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $returnPath = $this->alias->email;
         $returnPath = $this->alias->email;
 
 
         if ($this->alias->isCustomDomain()) {
         if ($this->alias->isCustomDomain()) {
-            if ($this->alias->aliasable->isVerifiedForSending()) {
-                if (config('anonaddy.dkim_signing_key')) {
-                    $this->dkimSigner = new Swift_Signers_DKIMSigner(config('anonaddy.dkim_signing_key'), $this->alias->domain, config('anonaddy.dkim_selector'));
-                    $this->dkimSigner->ignoreHeader('List-Unsubscribe');
-                    $this->dkimSigner->ignoreHeader('Return-Path');
-                    $this->dkimSigner->ignoreHeader('Feedback-ID');
-                    $this->dkimSigner->ignoreHeader('Content-Type');
-                    $this->dkimSigner->ignoreHeader('Content-Description');
-                    $this->dkimSigner->ignoreHeader('Content-Disposition');
-                    $this->dkimSigner->ignoreHeader('Content-Transfer-Encoding');
-                    $this->dkimSigner->ignoreHeader('MIME-Version');
-                }
-            } else {
+            if (! $this->alias->aliasable->isVerifiedForSending()) {
                 if (! isset($replyToEmail)) {
                 if (! isset($replyToEmail)) {
                     $replyToEmail = $this->fromEmail;
                     $replyToEmail = $this->fromEmail;
                 }
                 }
@@ -153,8 +123,8 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $this->email =  $this
         $this->email =  $this
             ->from($this->fromEmail, base64_decode($this->displayFrom)." '".$this->sender."'")
             ->from($this->fromEmail, base64_decode($this->displayFrom)." '".$this->sender."'")
             ->subject($this->user->email_subject ?? base64_decode($this->emailSubject))
             ->subject($this->user->email_subject ?? base64_decode($this->emailSubject))
-            ->withSwiftMessage(function ($message) use ($returnPath) {
-                $message->setReturnPath($returnPath);
+            ->withSymfonyMessage(function (Email $message) use ($returnPath) {
+                $message->returnPath($returnPath);
 
 
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'F:' . $this->alias->id . ':anonaddy');
                         ->addTextHeader('Feedback-ID', 'F:' . $this->alias->id . ':anonaddy');
@@ -163,14 +133,14 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Alias-To', $this->alias->email);
                         ->addTextHeader('Alias-To', $this->alias->email);
 
 
-                if ($this->messageId) {
-                    $message->getHeaders()->remove('Message-ID');
+                $message->getHeaders()->remove('Message-ID');
 
 
-                    // We're not using $message->setId here because it can cause RFC exceptions
+                if ($this->messageId) {
                     $message->getHeaders()
                     $message->getHeaders()
-                            ->addTextHeader('Message-ID', base64_decode($this->messageId));
+                            ->addIdHeader('Message-ID', base64_decode($this->messageId));
                 } else {
                 } else {
-                    $message->setId(bin2hex(random_bytes(16)).'@'.$this->alias->domain);
+                    $message->getHeaders()
+                            ->addIdHeader('Message-ID', bin2hex(random_bytes(16)).'@'.$this->alias->domain);
                 }
                 }
 
 
                 if ($this->listUnsubscribe) {
                 if ($this->listUnsubscribe) {
@@ -214,33 +184,17 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
                             ->addTextHeader('Sender', base64_decode($this->originalSenderHeader));
                             ->addTextHeader('Sender', base64_decode($this->originalSenderHeader));
                 }
                 }
 
 
-                if ($this->encryptedParts) {
-                    $alreadyEncryptedSigner = new AlreadyEncryptedSigner($this->encryptedParts);
-
-                    $message->attachSigner($alreadyEncryptedSigner);
-                }
-
-                if ($this->openpgpsigner) {
-                    $message->attachSigner($this->openpgpsigner);
-                }
-
-                if ($this->dkimSigner) {
-                    $message->attachSigner($this->dkimSigner);
-                }
-
                 if ($this->emailInlineAttachments) {
                 if ($this->emailInlineAttachments) {
                     foreach ($this->emailInlineAttachments as $attachment) {
                     foreach ($this->emailInlineAttachments as $attachment) {
-                        $image = new Swift_Image(base64_decode($attachment['stream']), base64_decode($attachment['file_name']), base64_decode($attachment['mime']));
+                        $part = new InlineImagePart(base64_decode($attachment['stream']), base64_decode($attachment['file_name']), base64_decode($attachment['mime']));
 
 
-                        $cids[] = 'cid:' . base64_decode($attachment['contentId']);
-                        $newCids[] = $message->embed($image);
-                    }
+                        $part->asInline();
 
 
-                    $message->getHeaders()
-                            ->addTextHeader('X-Old-Cids', implode(',', $cids));
+                        $part->setContentId(base64_decode($attachment['contentId']));
+                        $part->setFileName(base64_decode($attachment['file_name']));
 
 
-                    $message->getHeaders()
-                            ->addTextHeader('X-New-Cids', implode(',', $newCids));
+                        $message->attachPart($part);
+                    }
                 }
                 }
 
 
                 if ($this->originalCc) {
                 if ($this->originalCc) {
@@ -289,10 +243,15 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
             'location' => $this->bannerLocation,
             'location' => $this->bannerLocation,
             'deactivateUrl' => $this->deactivateUrl,
             'deactivateUrl' => $this->deactivateUrl,
             'aliasEmail' => $this->alias->email,
             'aliasEmail' => $this->alias->email,
+            'aliasDomain' => $this->alias->domain,
             'aliasDescription' => $this->alias->description,
             'aliasDescription' => $this->alias->description,
+            'recipientId' => $this->recipientId,
+            'fingerprint' => $this->fingerprint,
+            'encryptedParts' => $this->encryptedParts,
             'fromEmail' => $this->sender,
             'fromEmail' => $this->sender,
             'replacedSubject' => $this->replacedSubject,
             'replacedSubject' => $this->replacedSubject,
-            'shouldBlock' => $this->size === 0
+            'shouldBlock' => $this->size === 0,
+            'needsDkimSignature' => $this->needsDkimSignature()
         ]);
         ]);
 
 
         if (isset($replyToEmail)) {
         if (isset($replyToEmail)) {
@@ -350,4 +309,9 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
     {
     {
         return $this->encryptedParts || preg_match('/^-----BEGIN PGP MESSAGE-----([A-Za-z0-9+=\/\n]+)-----END PGP MESSAGE-----$/', base64_decode($this->emailText));
         return $this->encryptedParts || preg_match('/^-----BEGIN PGP MESSAGE-----([A-Za-z0-9+=\/\n]+)-----END PGP MESSAGE-----$/', base64_decode($this->emailText));
     }
     }
+
+    private function needsDkimSignature()
+    {
+        return $this->alias->isCustomDomain() ? $this->alias->aliasable->isVerifiedForSending() : false;
+    }
 }
 }

+ 27 - 43
app/Mail/ReplyToEmail.php

@@ -2,7 +2,7 @@
 
 
 namespace App\Mail;
 namespace App\Mail;
 
 
-use App\Helpers\AlreadyEncryptedSigner;
+use App\CustomMailDriver\Mime\Part\InlineImagePart;
 use App\Models\Alias;
 use App\Models\Alias;
 use App\Models\EmailData;
 use App\Models\EmailData;
 use App\Models\User;
 use App\Models\User;
@@ -13,12 +13,13 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\SerializesModels;
-use Swift_Image;
-use Swift_Signers_DKIMSigner;
+use Symfony\Component\Mime\Email;
 
 
 class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
 class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
 {
 {
-    use Queueable, SerializesModels, CheckUserRules;
+    use Queueable;
+    use SerializesModels;
+    use CheckUserRules;
 
 
     protected $email;
     protected $email;
     protected $user;
     protected $user;
@@ -29,7 +30,6 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
     protected $emailHtml;
     protected $emailHtml;
     protected $emailAttachments;
     protected $emailAttachments;
     protected $emailInlineAttachments;
     protected $emailInlineAttachments;
-    protected $dkimSigner;
     protected $encryptedParts;
     protected $encryptedParts;
     protected $displayFrom;
     protected $displayFrom;
     protected $fromEmail;
     protected $fromEmail;
@@ -69,21 +69,7 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $returnPath = $this->alias->email;
         $returnPath = $this->alias->email;
 
 
         if ($this->alias->isCustomDomain()) {
         if ($this->alias->isCustomDomain()) {
-            if ($this->alias->aliasable->isVerifiedForSending()) {
-                $this->fromEmail = $this->alias->email;
-
-                if (config('anonaddy.dkim_signing_key')) {
-                    $this->dkimSigner = new Swift_Signers_DKIMSigner(config('anonaddy.dkim_signing_key'), $this->alias->domain, config('anonaddy.dkim_selector'));
-                    $this->dkimSigner->ignoreHeader('List-Unsubscribe');
-                    $this->dkimSigner->ignoreHeader('Return-Path');
-                    $this->dkimSigner->ignoreHeader('Feedback-ID');
-                    $this->dkimSigner->ignoreHeader('Content-Type');
-                    $this->dkimSigner->ignoreHeader('Content-Description');
-                    $this->dkimSigner->ignoreHeader('Content-Disposition');
-                    $this->dkimSigner->ignoreHeader('Content-Transfer-Encoding');
-                    $this->dkimSigner->ignoreHeader('MIME-Version');
-                }
-            } else {
+            if (! $this->alias->aliasable->isVerifiedForSending()) {
                 $this->fromEmail = config('mail.from.address');
                 $this->fromEmail = config('mail.from.address');
                 $returnPath = config('anonaddy.return_path');
                 $returnPath = config('anonaddy.return_path');
             }
             }
@@ -94,14 +80,16 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $this->email =  $this
         $this->email =  $this
             ->from($this->fromEmail, $this->displayFrom)
             ->from($this->fromEmail, $this->displayFrom)
             ->subject(base64_decode($this->emailSubject))
             ->subject(base64_decode($this->emailSubject))
-            ->withSwiftMessage(function ($message) use ($returnPath) {
-                $message->setReturnPath($returnPath);
+            ->withSymfonyMessage(function (Email $message) use ($returnPath) {
+                $message->returnPath($returnPath);
 
 
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'R:' . $this->alias->id . ':anonaddy');
                         ->addTextHeader('Feedback-ID', 'R:' . $this->alias->id . ':anonaddy');
 
 
                 // Message-ID is replaced on replies as it can leak parts of the real email
                 // Message-ID is replaced on replies as it can leak parts of the real email
-                $message->setId(bin2hex(random_bytes(16)).'@'.$this->alias->domain);
+                $message->getHeaders()->remove('Message-ID');
+                $message->getHeaders()
+                            ->addIdHeader('Message-ID', bin2hex(random_bytes(16)).'@'.$this->alias->domain);
 
 
                 if ($this->inReplyTo) {
                 if ($this->inReplyTo) {
                     $message->getHeaders()
                     $message->getHeaders()
@@ -113,29 +101,17 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
                             ->addTextHeader('References', base64_decode($this->references));
                             ->addTextHeader('References', base64_decode($this->references));
                 }
                 }
 
 
-                if ($this->encryptedParts) {
-                    $alreadyEncryptedSigner = new AlreadyEncryptedSigner($this->encryptedParts);
-
-                    $message->attachSigner($alreadyEncryptedSigner);
-                }
-
-                if ($this->dkimSigner) {
-                    $message->attachSigner($this->dkimSigner);
-                }
-
                 if ($this->emailInlineAttachments) {
                 if ($this->emailInlineAttachments) {
                     foreach ($this->emailInlineAttachments as $attachment) {
                     foreach ($this->emailInlineAttachments as $attachment) {
-                        $image = new Swift_Image(base64_decode($attachment['stream']), base64_decode($attachment['file_name']), base64_decode($attachment['mime']));
+                        $part = new InlineImagePart(base64_decode($attachment['stream']), base64_decode($attachment['file_name']), base64_decode($attachment['mime']));
 
 
-                        $cids[] = 'cid:' . base64_decode($attachment['contentId']);
-                        $newCids[] = $message->embed($image);
-                    }
+                        $part->asInline();
 
 
-                    $message->getHeaders()
-                            ->addTextHeader('X-Old-Cids', implode(',', $cids));
+                        $part->setContentId(base64_decode($attachment['contentId']));
+                        $part->setFileName(base64_decode($attachment['file_name']));
 
 
-                    $message->getHeaders()
-                            ->addTextHeader('X-New-Cids', implode(',', $newCids));
+                        $message->attachPart($part);
+                    }
                 }
                 }
             });
             });
 
 
@@ -169,10 +145,13 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $this->checkRules('Replies');
         $this->checkRules('Replies');
 
 
         $this->email->with([
         $this->email->with([
-            'shouldBlock' => $this->size === 0
+            'shouldBlock' => $this->size === 0,
+            'encryptedParts' => $this->encryptedParts,
+            'needsDkimSignature' => $this->needsDkimSignature(),
+            'aliasDomain' => $this->alias->domain
         ]);
         ]);
 
 
-        if ($this->alias->isCustomDomain() && !$this->dkimSigner) {
+        if ($this->alias->isCustomDomain() && ! $this->needsDkimSignature()) {
             $this->email->replyTo($this->alias->email, $this->displayFrom);
             $this->email->replyTo($this->alias->email, $this->displayFrom);
         }
         }
 
 
@@ -220,4 +199,9 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
             'attempted_at' => now()
             'attempted_at' => now()
         ]);
         ]);
     }
     }
+
+    private function needsDkimSignature()
+    {
+        return $this->alias->isCustomDomain() ? $this->alias->aliasable->isVerifiedForSending() : false;
+    }
 }
 }

+ 27 - 43
app/Mail/SendFromEmail.php

@@ -2,7 +2,7 @@
 
 
 namespace App\Mail;
 namespace App\Mail;
 
 
-use App\Helpers\AlreadyEncryptedSigner;
+use App\CustomMailDriver\Mime\Part\InlineImagePart;
 use App\Models\Alias;
 use App\Models\Alias;
 use App\Models\EmailData;
 use App\Models\EmailData;
 use App\Models\User;
 use App\Models\User;
@@ -13,12 +13,13 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\SerializesModels;
-use Swift_Image;
-use Swift_Signers_DKIMSigner;
+use Symfony\Component\Mime\Email;
 
 
 class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
 class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
 {
 {
-    use Queueable, SerializesModels, CheckUserRules;
+    use Queueable;
+    use SerializesModels;
+    use CheckUserRules;
 
 
     protected $email;
     protected $email;
     protected $user;
     protected $user;
@@ -29,7 +30,6 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
     protected $emailHtml;
     protected $emailHtml;
     protected $emailAttachments;
     protected $emailAttachments;
     protected $emailInlineAttachments;
     protected $emailInlineAttachments;
-    protected $dkimSigner;
     protected $encryptedParts;
     protected $encryptedParts;
     protected $displayFrom;
     protected $displayFrom;
     protected $fromEmail;
     protected $fromEmail;
@@ -65,21 +65,7 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $returnPath = $this->alias->email;
         $returnPath = $this->alias->email;
 
 
         if ($this->alias->isCustomDomain()) {
         if ($this->alias->isCustomDomain()) {
-            if ($this->alias->aliasable->isVerifiedForSending()) {
-                $this->fromEmail = $this->alias->email;
-
-                if (config('anonaddy.dkim_signing_key')) {
-                    $this->dkimSigner = new Swift_Signers_DKIMSigner(config('anonaddy.dkim_signing_key'), $this->alias->domain, config('anonaddy.dkim_selector'));
-                    $this->dkimSigner->ignoreHeader('List-Unsubscribe');
-                    $this->dkimSigner->ignoreHeader('Return-Path');
-                    $this->dkimSigner->ignoreHeader('Feedback-ID');
-                    $this->dkimSigner->ignoreHeader('Content-Type');
-                    $this->dkimSigner->ignoreHeader('Content-Description');
-                    $this->dkimSigner->ignoreHeader('Content-Disposition');
-                    $this->dkimSigner->ignoreHeader('Content-Transfer-Encoding');
-                    $this->dkimSigner->ignoreHeader('MIME-Version');
-                }
-            } else {
+            if (! $this->alias->aliasable->isVerifiedForSending()) {
                 $this->fromEmail = config('mail.from.address');
                 $this->fromEmail = config('mail.from.address');
                 $returnPath = config('anonaddy.return_path');
                 $returnPath = config('anonaddy.return_path');
             }
             }
@@ -90,38 +76,28 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $this->email =  $this
         $this->email =  $this
             ->from($this->fromEmail, $this->displayFrom)
             ->from($this->fromEmail, $this->displayFrom)
             ->subject(base64_decode($this->emailSubject))
             ->subject(base64_decode($this->emailSubject))
-            ->withSwiftMessage(function ($message) use ($returnPath) {
-                $message->setReturnPath($returnPath);
+            ->withSymfonyMessage(function (Email $message) use ($returnPath) {
+                $message->returnPath($returnPath);
 
 
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'S:' . $this->alias->id . ':anonaddy');
                         ->addTextHeader('Feedback-ID', 'S:' . $this->alias->id . ':anonaddy');
 
 
                 // Message-ID is replaced on send from as it can leak parts of the real email
                 // Message-ID is replaced on send from as it can leak parts of the real email
-                $message->setId(bin2hex(random_bytes(16)).'@'.$this->alias->domain);
-
-                if ($this->encryptedParts) {
-                    $alreadyEncryptedSigner = new AlreadyEncryptedSigner($this->encryptedParts);
-
-                    $message->attachSigner($alreadyEncryptedSigner);
-                }
-
-                if ($this->dkimSigner) {
-                    $message->attachSigner($this->dkimSigner);
-                }
+                $message->getHeaders()->remove('Message-ID');
+                $message->getHeaders()
+                            ->addIdHeader('Message-ID', bin2hex(random_bytes(16)).'@'.$this->alias->domain);
 
 
                 if ($this->emailInlineAttachments) {
                 if ($this->emailInlineAttachments) {
                     foreach ($this->emailInlineAttachments as $attachment) {
                     foreach ($this->emailInlineAttachments as $attachment) {
-                        $image = new Swift_Image(base64_decode($attachment['stream']), base64_decode($attachment['file_name']), base64_decode($attachment['mime']));
+                        $part = new InlineImagePart(base64_decode($attachment['stream']), base64_decode($attachment['file_name']), base64_decode($attachment['mime']));
 
 
-                        $cids[] = 'cid:' . base64_decode($attachment['contentId']);
-                        $newCids[] = $message->embed($image);
-                    }
+                        $part->asInline();
 
 
-                    $message->getHeaders()
-                            ->addTextHeader('X-Old-Cids', implode(',', $cids));
+                        $part->setContentId(base64_decode($attachment['contentId']));
+                        $part->setFileName(base64_decode($attachment['file_name']));
 
 
-                    $message->getHeaders()
-                            ->addTextHeader('X-New-Cids', implode(',', $newCids));
+                        $message->attachPart($part);
+                    }
                 }
                 }
             });
             });
 
 
@@ -155,10 +131,13 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         $this->checkRules('Sends');
         $this->checkRules('Sends');
 
 
         $this->email->with([
         $this->email->with([
-            'shouldBlock' => $this->size === 0
+            'shouldBlock' => $this->size === 0,
+            'encryptedParts' => $this->encryptedParts,
+            'needsDkimSignature' => $this->needsDkimSignature(),
+            'aliasDomain' => $this->alias->domain
         ]);
         ]);
 
 
-        if ($this->alias->isCustomDomain() && !$this->dkimSigner) {
+        if ($this->alias->isCustomDomain() && ! $this->needsDkimSignature()) {
             $this->email->replyTo($this->alias->email, $this->displayFrom);
             $this->email->replyTo($this->alias->email, $this->displayFrom);
         }
         }
 
 
@@ -206,4 +185,9 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
             'attempted_at' => now()
             'attempted_at' => now()
         ]);
         ]);
     }
     }
+
+    private function needsDkimSignature()
+    {
+        return $this->alias->isCustomDomain() ? $this->alias->aliasable->isVerifiedForSending() : false;
+    }
 }
 }

+ 13 - 3
app/Mail/TokenExpiringSoon.php

@@ -8,12 +8,15 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\SerializesModels;
+use Symfony\Component\Mime\Email;
 
 
 class TokenExpiringSoon extends Mailable implements ShouldQueue, ShouldBeEncrypted
 class TokenExpiringSoon extends Mailable implements ShouldQueue, ShouldBeEncrypted
 {
 {
-    use Queueable, SerializesModels;
+    use Queueable;
+    use SerializesModels;
 
 
     protected $user;
     protected $user;
+    protected $recipient;
 
 
     /**
     /**
      * Create a new message instance.
      * Create a new message instance.
@@ -23,6 +26,7 @@ class TokenExpiringSoon extends Mailable implements ShouldQueue, ShouldBeEncrypt
     public function __construct(User $user)
     public function __construct(User $user)
     {
     {
         $this->user = $user;
         $this->user = $user;
+        $this->recipient = $user->defaultRecipient;
     }
     }
 
 
     /**
     /**
@@ -35,7 +39,13 @@ class TokenExpiringSoon extends Mailable implements ShouldQueue, ShouldBeEncrypt
         return $this
         return $this
             ->subject("Your AnonAddy API token expires soon")
             ->subject("Your AnonAddy API token expires soon")
             ->markdown('mail.token_expiring_soon', [
             ->markdown('mail.token_expiring_soon', [
-                'user' => $this->user
-            ]);
+                'user' => $this->user,
+                'recipientId' => $this->recipient->id,
+                'fingerprint' => $this->recipient->should_encrypt ? $this->recipient->fingerprint : null
+            ])
+            ->withSymfonyMessage(function (Email $message) {
+                $message->getHeaders()
+                        ->addTextHeader('Feedback-ID', 'TES:anonaddy');
+            });
     }
     }
 }
 }

+ 4 - 1
app/Models/Alias.php

@@ -11,7 +11,10 @@ use Illuminate\Support\Str;
 
 
 class Alias extends Model
 class Alias extends Model
 {
 {
-    use SoftDeletes, HasUuid, HasEncryptedAttributes, HasFactory;
+    use SoftDeletes;
+    use HasUuid;
+    use HasEncryptedAttributes;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 

+ 2 - 1
app/Models/DeletedUsername.php

@@ -8,7 +8,8 @@ use Illuminate\Database\Eloquent\Model;
 
 
 class DeletedUsername extends Model
 class DeletedUsername extends Model
 {
 {
-    use HasEncryptedAttributes, HasFactory;
+    use HasEncryptedAttributes;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 

+ 3 - 1
app/Models/Domain.php

@@ -11,7 +11,9 @@ use Illuminate\Support\Facades\App;
 
 
 class Domain extends Model
 class Domain extends Model
 {
 {
-    use HasUuid, HasEncryptedAttributes, HasFactory;
+    use HasUuid;
+    use HasEncryptedAttributes;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 

+ 2 - 1
app/Models/EmailData.php

@@ -2,6 +2,7 @@
 
 
 namespace App\Models;
 namespace App\Models;
 
 
+use Illuminate\Support\Str;
 use PhpMimeMailParser\Parser;
 use PhpMimeMailParser\Parser;
 
 
 class EmailData
 class EmailData
@@ -28,7 +29,7 @@ class EmailData
         $this->attachments = [];
         $this->attachments = [];
         $this->inlineAttachments = [];
         $this->inlineAttachments = [];
         $this->size = $size;
         $this->size = $size;
-        $this->messageId = base64_encode($parser->getHeader('Message-ID'));
+        $this->messageId = base64_encode(Str::remove(['<', '>'], $parser->getHeader('Message-ID')));
         $this->listUnsubscribe = base64_encode($parser->getHeader('List-Unsubscribe'));
         $this->listUnsubscribe = base64_encode($parser->getHeader('List-Unsubscribe'));
         $this->inReplyTo = base64_encode($parser->getHeader('In-Reply-To'));
         $this->inReplyTo = base64_encode($parser->getHeader('In-Reply-To'));
         $this->references = base64_encode($parser->getHeader('References'));
         $this->references = base64_encode($parser->getHeader('References'));

+ 3 - 1
app/Models/FailedDelivery.php

@@ -10,7 +10,9 @@ use Illuminate\Database\Eloquent\Model;
 
 
 class FailedDelivery extends Model
 class FailedDelivery extends Model
 {
 {
-    use HasUuid, HasEncryptedAttributes, HasFactory;
+    use HasUuid;
+    use HasEncryptedAttributes;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 

+ 6 - 3
app/Models/Recipient.php

@@ -13,7 +13,10 @@ use Illuminate\Notifications\Notifiable;
 
 
 class Recipient extends Model
 class Recipient extends Model
 {
 {
-    use Notifiable, HasUuid, HasEncryptedAttributes, HasFactory;
+    use Notifiable;
+    use HasUuid;
+    use HasEncryptedAttributes;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 
@@ -140,7 +143,7 @@ class Recipient extends Model
      */
      */
     public function sendEmailVerificationNotification()
     public function sendEmailVerificationNotification()
     {
     {
-        $this->notify(new CustomVerifyEmail);
+        $this->notify(new CustomVerifyEmail());
     }
     }
 
 
     /**
     /**
@@ -150,7 +153,7 @@ class Recipient extends Model
      */
      */
     public function sendUsernameReminderNotification()
     public function sendUsernameReminderNotification()
     {
     {
-        $this->notify(new UsernameReminder);
+        $this->notify(new UsernameReminder());
     }
     }
 
 
     /**
     /**

+ 2 - 1
app/Models/Rule.php

@@ -8,7 +8,8 @@ use Illuminate\Database\Eloquent\Model;
 
 
 class Rule extends Model
 class Rule extends Model
 {
 {
-    use HasUuid, HasFactory;
+    use HasUuid;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 

+ 7 - 3
app/Models/User.php

@@ -16,7 +16,11 @@ use Laravel\Passport\HasApiTokens;
 
 
 class User extends Authenticatable implements MustVerifyEmail
 class User extends Authenticatable implements MustVerifyEmail
 {
 {
-    use Notifiable, HasUuid, HasEncryptedAttributes, HasApiTokens, HasFactory;
+    use Notifiable;
+    use HasUuid;
+    use HasEncryptedAttributes;
+    use HasApiTokens;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 
@@ -316,7 +320,7 @@ class User extends Authenticatable implements MustVerifyEmail
      */
      */
     public function sendEmailVerificationNotification()
     public function sendEmailVerificationNotification()
     {
     {
-        $this->notify(new CustomVerifyEmail);
+        $this->notify(new CustomVerifyEmail());
     }
     }
 
 
     public function hasVerifiedDefaultRecipient()
     public function hasVerifiedDefaultRecipient()
@@ -465,7 +469,7 @@ class User extends Authenticatable implements MustVerifyEmail
             ->implode('.').mt_rand(0, 999);
             ->implode('.').mt_rand(0, 999);
     }
     }
 
 
-    public function generateRandomCharacterLocalPart(int $length) : string
+    public function generateRandomCharacterLocalPart(int $length): string
     {
     {
         $alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
         $alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
 
 

+ 3 - 1
app/Models/Username.php

@@ -9,7 +9,9 @@ use Illuminate\Database\Eloquent\Model;
 
 
 class Username extends Model
 class Username extends Model
 {
 {
-    use HasUuid, HasEncryptedAttributes, HasFactory;
+    use HasUuid;
+    use HasEncryptedAttributes;
+    use HasFactory;
 
 
     public $incrementing = false;
     public $incrementing = false;
 
 

+ 3 - 2
app/Notifications/CustomVerifyEmail.php

@@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Config;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Facades\Lang;
 use Illuminate\Support\Facades\Lang;
 use Illuminate\Support\Facades\URL;
 use Illuminate\Support\Facades\URL;
+use Symfony\Component\Mime\Email;
 
 
 class CustomVerifyEmail extends VerifyEmail implements ShouldQueue, ShouldBeEncrypted
 class CustomVerifyEmail extends VerifyEmail implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -35,13 +36,13 @@ class CustomVerifyEmail extends VerifyEmail implements ShouldQueue, ShouldBeEncr
         $feedbackId = $notifiable instanceof User ? 'VU:anonaddy' : 'VR:anonaddy';
         $feedbackId = $notifiable instanceof User ? 'VU:anonaddy' : 'VR:anonaddy';
         $recipientId = $notifiable instanceof User ? $notifiable->default_recipient_id : $notifiable->id;
         $recipientId = $notifiable instanceof User ? $notifiable->default_recipient_id : $notifiable->id;
 
 
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject(Lang::get('Verify Email Address'))
             ->subject(Lang::get('Verify Email Address'))
             ->markdown('mail.verify_email', [
             ->markdown('mail.verify_email', [
                 'verificationUrl' => $verificationUrl,
                 'verificationUrl' => $verificationUrl,
                 'recipientId' => $recipientId
                 'recipientId' => $recipientId
             ])
             ])
-            ->withSwiftMessage(function ($message) use ($feedbackId) {
+            ->withSymfonyMessage(function (Email $message) use ($feedbackId) {
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', $feedbackId);
                         ->addTextHeader('Feedback-ID', $feedbackId);
             });
             });

+ 6 - 26
app/Notifications/DefaultRecipientUpdated.php

@@ -2,13 +2,12 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class DefaultRecipientUpdated extends Notification implements ShouldQueue, ShouldBeEncrypted
 class DefaultRecipientUpdated extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -45,36 +44,17 @@ class DefaultRecipientUpdated extends Notification implements ShouldQueue, Shoul
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
-        $fingerprint = $notifiable->should_encrypt ? $notifiable->fingerprint : null;
-
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $notifiable->update(['should_encrypt' => false]);
-
-                $notifiable->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject("Your default recipient has just been updated")
             ->subject("Your default recipient has just been updated")
             ->markdown('mail.default_recipient_updated', [
             ->markdown('mail.default_recipient_updated', [
                 'defaultRecipient' => $notifiable->email,
                 'defaultRecipient' => $notifiable->email,
-                'newDefaultRecipient' => $this->newDefaultRecipient
+                'newDefaultRecipient' => $this->newDefaultRecipient,
+                'recipientId' => $notifiable->id,
+                'fingerprint' => $notifiable->should_encrypt ? $notifiable->fingerprint : null
             ])
             ])
-            ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+            ->withSymfonyMessage(function (Email $message) {
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'DRU:anonaddy');
                         ->addTextHeader('Feedback-ID', 'DRU:anonaddy');
-
-                if ($openpgpsigner) {
-                    $message->attachSigner($openpgpsigner);
-                }
             });
             });
     }
     }
 
 

+ 6 - 24
app/Notifications/DisallowedReplySendAttempt.php

@@ -2,14 +2,13 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class DisallowedReplySendAttempt extends Notification implements ShouldQueue, ShouldBeEncrypted
 class DisallowedReplySendAttempt extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -52,38 +51,21 @@ class DisallowedReplySendAttempt extends Notification implements ShouldQueue, Sh
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
         $fingerprint = $notifiable->should_encrypt ? $notifiable->fingerprint : null;
         $fingerprint = $notifiable->should_encrypt ? $notifiable->fingerprint : null;
 
 
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $notifiable->update(['should_encrypt' => false]);
-
-                $notifiable->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject('Disallowed reply/send from alias')
             ->subject('Disallowed reply/send from alias')
             ->markdown('mail.disallowed_reply_send_attempt', [
             ->markdown('mail.disallowed_reply_send_attempt', [
                 'aliasEmail' => $this->aliasEmail,
                 'aliasEmail' => $this->aliasEmail,
                 'recipient' => $this->recipient,
                 'recipient' => $this->recipient,
                 'destination' => $this->destination,
                 'destination' => $this->destination,
-                'authenticationResults' => $this->authenticationResults
+                'authenticationResults' => $this->authenticationResults,
+                'recipientId' => $notifiable->id,
+                'fingerprint' => $fingerprint
             ])
             ])
-            ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+            ->withSymfonyMessage(function (Email $message) {
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'DRSA:anonaddy');
                         ->addTextHeader('Feedback-ID', 'DRSA:anonaddy');
-
-                if ($openpgpsigner) {
-                    $message->attachSigner($openpgpsigner);
-                }
             });
             });
     }
     }
 
 

+ 6 - 24
app/Notifications/DomainMxRecordsInvalid.php

@@ -2,13 +2,12 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class DomainMxRecordsInvalid extends Notification implements ShouldQueue, ShouldBeEncrypted
 class DomainMxRecordsInvalid extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -45,36 +44,19 @@ class DomainMxRecordsInvalid extends Notification implements ShouldQueue, Should
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
         $recipient = $notifiable->defaultRecipient;
         $recipient = $notifiable->defaultRecipient;
         $fingerprint = $recipient->should_encrypt ? $recipient->fingerprint : null;
         $fingerprint = $recipient->should_encrypt ? $recipient->fingerprint : null;
 
 
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $recipient->update(['should_encrypt' => false]);
-
-                $recipient->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject("Your domain's MX records no longer point to AnonAddy")
             ->subject("Your domain's MX records no longer point to AnonAddy")
             ->markdown('mail.domain_mx_records_invalid', [
             ->markdown('mail.domain_mx_records_invalid', [
-                'domain' => $this->domain
+                'domain' => $this->domain,
+                'recipientId' => $recipient->_id,
+                'fingerprint' => $fingerprint
             ])
             ])
-            ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+            ->withSymfonyMessage(function (Email $message) {
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'DMI:anonaddy');
                         ->addTextHeader('Feedback-ID', 'DMI:anonaddy');
-
-                if ($openpgpsigner) {
-                    $message->attachSigner($openpgpsigner);
-                }
             });
             });
     }
     }
 
 

+ 6 - 24
app/Notifications/DomainUnverifiedForSending.php

@@ -2,13 +2,12 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class DomainUnverifiedForSending extends Notification implements ShouldQueue, ShouldBeEncrypted
 class DomainUnverifiedForSending extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -47,37 +46,20 @@ class DomainUnverifiedForSending extends Notification implements ShouldQueue, Sh
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
         $recipient = $notifiable->defaultRecipient;
         $recipient = $notifiable->defaultRecipient;
         $fingerprint = $recipient->should_encrypt ? $recipient->fingerprint : null;
         $fingerprint = $recipient->should_encrypt ? $recipient->fingerprint : null;
 
 
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $recipient->update(['should_encrypt' => false]);
-
-                $recipient->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject("Your domain has been unverified for sending on AnonAddy")
             ->subject("Your domain has been unverified for sending on AnonAddy")
             ->markdown('mail.domain_unverified_for_sending', [
             ->markdown('mail.domain_unverified_for_sending', [
                 'domain' => $this->domain,
                 'domain' => $this->domain,
-                'reason' => $this->reason
+                'reason' => $this->reason,
+                'recipientId' => $recipient->id,
+                'fingerprint' => $fingerprint
             ])
             ])
-            ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+            ->withSymfonyMessage(function (Email $message) {
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'DUS:anonaddy');
                         ->addTextHeader('Feedback-ID', 'DUS:anonaddy');
-
-                if ($openpgpsigner) {
-                    $message->attachSigner($openpgpsigner);
-                }
             });
             });
     }
     }
 
 

+ 5 - 26
app/Notifications/FailedDeliveryNotification.php

@@ -2,13 +2,11 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
-use Swift_SwiftException;
 
 
 class FailedDeliveryNotification extends Notification implements ShouldQueue, ShouldBeEncrypted
 class FailedDeliveryNotification extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -49,37 +47,18 @@ class FailedDeliveryNotification extends Notification implements ShouldQueue, Sh
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
-        $fingerprint = $notifiable->should_encrypt ? $notifiable->fingerprint : null;
-
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $notifiable->update(['should_encrypt' => false]);
-
-                $notifiable->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
                     ->subject("New failed delivery on AnonAddy")
                     ->subject("New failed delivery on AnonAddy")
                     ->markdown('mail.failed_delivery_notification', [
                     ->markdown('mail.failed_delivery_notification', [
                         'aliasEmail' => $this->aliasEmail,
                         'aliasEmail' => $this->aliasEmail,
                         'originalSender' => $this->originalSender,
                         'originalSender' => $this->originalSender,
-                        'originalSubject' => $this->originalSubject
+                        'originalSubject' => $this->originalSubject,
+                        'recipientId' => $notifiable->id,
+                        'fingerprint' => $notifiable->should_encrypt ? $notifiable->fingerprint : null
                     ])
                     ])
-                    ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+                    ->withSymfonyMessage(function ($message) {
                         $message->getHeaders()
                         $message->getHeaders()
                                 ->addTextHeader('Feedback-ID', 'FDN:anonaddy');
                                 ->addTextHeader('Feedback-ID', 'FDN:anonaddy');
-
-                        if ($openpgpsigner) {
-                            $message->attachSigner($openpgpsigner);
-                        }
                     });
                     });
     }
     }
 
 

+ 7 - 2
app/Notifications/GpgKeyExpired.php

@@ -7,6 +7,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
+use Symfony\Component\Mime\Email;
 
 
 class GpgKeyExpired extends Notification implements ShouldQueue, ShouldBeEncrypted
 class GpgKeyExpired extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -31,11 +32,15 @@ class GpgKeyExpired extends Notification implements ShouldQueue, ShouldBeEncrypt
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject("Your GPG key has expired on AnonAddy")
             ->subject("Your GPG key has expired on AnonAddy")
             ->markdown('mail.gpg_key_expired', [
             ->markdown('mail.gpg_key_expired', [
                 'recipient' => $notifiable
                 'recipient' => $notifiable
-            ]);
+            ])
+            ->withSymfonyMessage(function (Email $message) {
+                $message->getHeaders()
+                        ->addTextHeader('Feedback-ID', 'GKE:anonaddy');
+            });
     }
     }
 
 
     /**
     /**

+ 6 - 24
app/Notifications/NearBandwidthLimit.php

@@ -2,13 +2,12 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class NearBandwidthLimit extends Notification implements ShouldQueue, ShouldBeEncrypted
 class NearBandwidthLimit extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -47,39 +46,22 @@ class NearBandwidthLimit extends Notification implements ShouldQueue, ShouldBeEn
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
         $recipient = $notifiable->defaultRecipient;
         $recipient = $notifiable->defaultRecipient;
         $fingerprint = $recipient->should_encrypt ? $recipient->fingerprint : null;
         $fingerprint = $recipient->should_encrypt ? $recipient->fingerprint : null;
 
 
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $recipient->update(['should_encrypt' => false]);
-
-                $recipient->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
         ->subject("You're close to your bandwidth limit for ".$this->month)
         ->subject("You're close to your bandwidth limit for ".$this->month)
         ->markdown('mail.near_bandwidth_limit', [
         ->markdown('mail.near_bandwidth_limit', [
             'bandwidthUsage' => $notifiable->bandwidth_mb,
             'bandwidthUsage' => $notifiable->bandwidth_mb,
             'bandwidthLimit' => $notifiable->getBandwidthLimitMb(),
             'bandwidthLimit' => $notifiable->getBandwidthLimitMb(),
             'month' => $this->month,
             'month' => $this->month,
-            'reset' => $this->reset
+            'reset' => $this->reset,
+            'recipientId' => $recipient->id,
+            'fingerprint' => $fingerprint
         ])
         ])
-        ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+        ->withSymfonyMessage(function (Email $message) {
             $message->getHeaders()
             $message->getHeaders()
                     ->addTextHeader('Feedback-ID', 'NBL:anonaddy');
                     ->addTextHeader('Feedback-ID', 'NBL:anonaddy');
-
-            if ($openpgpsigner) {
-                $message->attachSigner($openpgpsigner);
-            }
         });
         });
     }
     }
 
 

+ 6 - 26
app/Notifications/SpamReplySendAttempt.php

@@ -2,14 +2,13 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBeEncrypted
 class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -52,38 +51,19 @@ class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBe
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
-        $fingerprint = $notifiable->should_encrypt ? $notifiable->fingerprint : null;
-
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $notifiable->update(['should_encrypt' => false]);
-
-                $notifiable->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject('Attempted reply/send from alias has failed')
             ->subject('Attempted reply/send from alias has failed')
             ->markdown('mail.spam_reply_send_attempt', [
             ->markdown('mail.spam_reply_send_attempt', [
                 'aliasEmail' => $this->aliasEmail,
                 'aliasEmail' => $this->aliasEmail,
                 'recipient' => $this->recipient,
                 'recipient' => $this->recipient,
                 'destination' => $this->destination,
                 'destination' => $this->destination,
-                'authenticationResults' => $this->authenticationResults
+                'authenticationResults' => $this->authenticationResults,
+                'recipientId' => $notifiable->id,
+                'fingerprint' => $notifiable->should_encrypt ? $notifiable->fingerprint : null
             ])
             ])
-            ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+            ->withSymfonyMessage(function (Email $message) {
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'SRSA:anonaddy');
                         ->addTextHeader('Feedback-ID', 'SRSA:anonaddy');
-
-                if ($openpgpsigner) {
-                    $message->attachSigner($openpgpsigner);
-                }
             });
             });
     }
     }
 
 

+ 6 - 26
app/Notifications/UsernameReminder.php

@@ -2,13 +2,12 @@
 
 
 namespace App\Notifications;
 namespace App\Notifications;
 
 
-use App\Helpers\OpenPGPSigner;
 use Illuminate\Bus\Queueable;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Notifications\Notification;
 use Illuminate\Notifications\Notification;
-use Swift_SwiftException;
+use Symfony\Component\Mime\Email;
 
 
 class UsernameReminder extends Notification implements ShouldQueue, ShouldBeEncrypted
 class UsernameReminder extends Notification implements ShouldQueue, ShouldBeEncrypted
 {
 {
@@ -33,35 +32,16 @@ class UsernameReminder extends Notification implements ShouldQueue, ShouldBeEncr
      */
      */
     public function toMail($notifiable)
     public function toMail($notifiable)
     {
     {
-        $openpgpsigner = null;
-        $fingerprint = $notifiable->should_encrypt ? $notifiable->fingerprint : null;
-
-        if ($fingerprint) {
-            try {
-                $openpgpsigner = OpenPGPSigner::newInstance(config('anonaddy.signing_key_fingerprint'), [], "~/.gnupg");
-                $openpgpsigner->addRecipient($fingerprint);
-            } catch (Swift_SwiftException $e) {
-                info($e->getMessage());
-                $openpgpsigner = null;
-
-                $notifiable->update(['should_encrypt' => false]);
-
-                $notifiable->notify(new GpgKeyExpired);
-            }
-        }
-
-        return (new MailMessage)
+        return (new MailMessage())
             ->subject("AnonAddy Username Reminder")
             ->subject("AnonAddy Username Reminder")
             ->markdown('mail.username_reminder', [
             ->markdown('mail.username_reminder', [
-                'username' => $notifiable->user->username
+                'username' => $notifiable->user->username,
+                'recipientId' => $notifiable->id,
+                'fingerprint' => $notifiable->should_encrypt ? $notifiable->fingerprint : null
             ])
             ])
-            ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+            ->withSymfonyMessage(function (Email $message) {
                 $message->getHeaders()
                 $message->getHeaders()
                         ->addTextHeader('Feedback-ID', 'UR:anonaddy');
                         ->addTextHeader('Feedback-ID', 'UR:anonaddy');
-
-                if ($openpgpsigner) {
-                    $message->attachSigner($openpgpsigner);
-                }
             });
             });
     }
     }
 
 

+ 0 - 3
app/Providers/AppServiceProvider.php

@@ -8,7 +8,6 @@ use Illuminate\Pagination\LengthAwarePaginator;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Facades\Blade;
 use Illuminate\Support\Facades\Blade;
 use Illuminate\Support\ServiceProvider;
 use Illuminate\Support\ServiceProvider;
-use Swift_Preferences;
 
 
 class AppServiceProvider extends ServiceProvider
 class AppServiceProvider extends ServiceProvider
 {
 {
@@ -31,8 +30,6 @@ class AppServiceProvider extends ServiceProvider
     {
     {
         Blade::withoutComponentTags();
         Blade::withoutComponentTags();
 
 
-        Swift_Preferences::getInstance()->setQPDotEscape(true);
-
         Builder::macro('jsonPaginate', function (int $maxResults = null, int $defaultSize = null) {
         Builder::macro('jsonPaginate', function (int $maxResults = null, int $defaultSize = null) {
             $maxResults = $maxResults ?? 100;
             $maxResults = $maxResults ?? 100;
             $defaultSize = $defaultSize ?? 100;
             $defaultSize = $defaultSize ?? 100;

+ 3 - 2
app/Providers/CustomMailServiceProvider.php

@@ -3,6 +3,7 @@
 namespace App\Providers;
 namespace App\Providers;
 
 
 use App\CustomMailDriver\CustomMailManager;
 use App\CustomMailDriver\CustomMailManager;
+use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Mail\MailServiceProvider;
 use Illuminate\Mail\MailServiceProvider;
 
 
 class CustomMailServiceProvider extends MailServiceProvider
 class CustomMailServiceProvider extends MailServiceProvider
@@ -14,11 +15,11 @@ class CustomMailServiceProvider extends MailServiceProvider
      */
      */
     protected function registerIlluminateMailer()
     protected function registerIlluminateMailer()
     {
     {
-        $this->app->singleton('mail.manager', function ($app) {
+        $this->app->singleton('mail.manager', static function (Application $app) {
             return new CustomMailManager($app);
             return new CustomMailManager($app);
         });
         });
 
 
-        $this->app->bind('mailer', function ($app) {
+        $this->app->bind('mailer', static function (Application $app) {
             return $app->make('mail.manager')->mailer();
             return $app->make('mail.manager')->mailer();
         });
         });
     }
     }

+ 10 - 0
app/Providers/EventServiceProvider.php

@@ -33,4 +33,14 @@ class EventServiceProvider extends ServiceProvider
     {
     {
         //
         //
     }
     }
+
+    /**
+     * Determine if events and listeners should be automatically discovered.
+     *
+     * @return bool
+     */
+    public function shouldDiscoverEvents()
+    {
+        return false;
+    }
 }
 }

+ 1 - 0
app/Providers/HelperServiceProvider.php

@@ -14,6 +14,7 @@ class HelperServiceProvider extends ServiceProvider
     public function register()
     public function register()
     {
     {
         require_once(app_path().'/Helpers/Helper.php');
         require_once(app_path().'/Helpers/Helper.php');
+        require_once(app_path().'/Helpers/GitVersionHelper.php');
     }
     }
 
 
     /**
     /**

+ 5 - 5
app/Providers/RouteServiceProvider.php

@@ -22,14 +22,14 @@ class RouteServiceProvider extends ServiceProvider
     /**
     /**
      * The path to the "home" route for your application.
      * The path to the "home" route for your application.
      *
      *
-     * This is used by Laravel authentication to redirect users after login.
+     * Typically, users are redirected here after authentication.s
      *
      *
      * @var string
      * @var string
      */
      */
     public const HOME = '/';
     public const HOME = '/';
 
 
     /**
     /**
-     * Define your route model bindings, pattern filters, etc.
+     * Define your route model bindings, pattern filters, and other route configuration.
      *
      *
      * @return void
      * @return void
      */
      */
@@ -38,9 +38,9 @@ class RouteServiceProvider extends ServiceProvider
         $this->configureRateLimiting();
         $this->configureRateLimiting();
 
 
         $this->routes(function () {
         $this->routes(function () {
-            Route::prefix('api')
-                ->middleware('api')
+            Route::middleware('api')
                 ->namespace($this->namespace)
                 ->namespace($this->namespace)
+                ->prefix('api')
                 ->group(base_path('routes/api.php'));
                 ->group(base_path('routes/api.php'));
 
 
             Route::middleware('web')
             Route::middleware('web')
@@ -57,7 +57,7 @@ class RouteServiceProvider extends ServiceProvider
     protected function configureRateLimiting()
     protected function configureRateLimiting()
     {
     {
         RateLimiter::for('api', function (Request $request) {
         RateLimiter::for('api', function (Request $request) {
-            return Limit::perMinute(60);
+            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
         });
         });
     }
     }
 }
 }

+ 3 - 23
app/Services/Webauthn.php

@@ -5,38 +5,18 @@ namespace App\Services;
 use App\Models\WebauthnKey;
 use App\Models\WebauthnKey;
 use Illuminate\Contracts\Auth\Authenticatable as User;
 use Illuminate\Contracts\Auth\Authenticatable as User;
 use LaravelWebauthn\Services\Webauthn as ServicesWebauthn;
 use LaravelWebauthn\Services\Webauthn as ServicesWebauthn;
-use Webauthn\PublicKeyCredentialSource;
 
 
 class Webauthn extends ServicesWebauthn
 class Webauthn extends ServicesWebauthn
 {
 {
-    /**
-     * Create a new key.
-     *
-     * @param  User  $user
-     * @param  string  $keyName
-     * @param  PublicKeyCredentialSource  $publicKeyCredentialSource
-     * @return WebauthnKey
-     */
-    public function create(User $user, string $keyName, PublicKeyCredentialSource $publicKeyCredentialSource)
-    {
-        $webauthnKey = new WebauthnKey();
-        $webauthnKey->user_id = $user->getAuthIdentifier();
-        $webauthnKey->name = $keyName;
-        $webauthnKey->publicKeyCredentialSource = $publicKeyCredentialSource;
-        $webauthnKey->save();
-
-        return $webauthnKey;
-    }
-
     /**
     /**
      * Test if the user has one or more webauthn key.
      * Test if the user has one or more webauthn key.
      *
      *
      * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
      * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
      * @return bool
      * @return bool
      */
      */
-    public function enabled(User $user): bool
+    public static function enabled(User $user): bool
     {
     {
-        return $this->webauthnEnabled() && $this->hasKey($user);
+        return static::webauthnEnabled() && static::hasKey($user);
     }
     }
 
 
     /**
     /**
@@ -45,7 +25,7 @@ class Webauthn extends ServicesWebauthn
      * @param User $user
      * @param User $user
      * @return bool
      * @return bool
      */
      */
-    public function hasKey(User $user): bool
+    public static function hasKey(User $user): bool
     {
     {
         return WebauthnKey::where('user_id', $user->getAuthIdentifier())->where('enabled', true)->count() > 0;
         return WebauthnKey::where('user_id', $user->getAuthIdentifier())->where('enabled', true)->count() > 0;
     }
     }

+ 2 - 5
app/Traits/CheckUserRules.php

@@ -118,11 +118,8 @@ trait CheckUserRules
                 break;
                 break;
             case 'encryption':
             case 'encryption':
                 if ($action['value'] == false) {
                 if ($action['value'] == false) {
-                    // detach the openpgpsigner from the email...
-                    if (isset($this->openpgpsigner)) {
-                        $this->email->withSwiftMessage(function ($message) {
-                            $message->detachSigner($this->openpgpsigner);
-                        });
+                    if (isset($this->fingerprint)) {
+                        $this->fingerprint = null;
                     }
                     }
                 }
                 }
                 break;
                 break;

+ 10 - 14
composer.json

@@ -7,32 +7,28 @@
     ],
     ],
     "license": "MIT",
     "license": "MIT",
     "require": {
     "require": {
-        "php": "^7.3|^8.0",
-        "asbiin/laravel-webauthn": "^2.0.0",
+        "php": "^8.0.2",
+        "asbiin/laravel-webauthn": "^3.0.0",
         "bacon/bacon-qr-code": "^2.0",
         "bacon/bacon-qr-code": "^2.0",
         "doctrine/dbal": "^3.0",
         "doctrine/dbal": "^3.0",
-        "fideloper/proxy": "^4.2",
-        "fruitcake/laravel-cors": "^2.0",
-        "guzzlehttp/guzzle": "^7.0.1",
-        "laravel/framework": "^8.0",
+        "guzzlehttp/guzzle": "^7.2",
+        "laravel/framework": "^9.11",
         "laravel/passport": "^10.0",
         "laravel/passport": "^10.0",
-        "laravel/tinker": "^2.0",
+        "laravel/tinker": "^2.7",
         "laravel/ui": "^3.0",
         "laravel/ui": "^3.0",
         "maatwebsite/excel": "^3.1",
         "maatwebsite/excel": "^3.1",
         "mews/captcha": "^3.0.0",
         "mews/captcha": "^3.0.0",
         "php-mime-mail-parser/php-mime-mail-parser": "^7.0",
         "php-mime-mail-parser/php-mime-mail-parser": "^7.0",
         "pragmarx/google2fa-laravel": "^2.0.0",
         "pragmarx/google2fa-laravel": "^2.0.0",
-        "pragmarx/version": "^1.2",
         "ramsey/uuid": "^4.0"
         "ramsey/uuid": "^4.0"
     },
     },
     "require-dev": {
     "require-dev": {
-        "beyondcode/laravel-dump-server": "^1.0",
-        "facade/ignition": "^2.3.6",
         "fakerphp/faker": "^1.9.1",
         "fakerphp/faker": "^1.9.1",
         "friendsofphp/php-cs-fixer": "^3.0.0",
         "friendsofphp/php-cs-fixer": "^3.0.0",
-        "mockery/mockery": "^1.3.1",
-        "nunomaduro/collision": "^5.0",
-        "phpunit/phpunit": "^9.3"
+        "mockery/mockery": "^1.4.4",
+        "nunomaduro/collision": "^6.1",
+        "phpunit/phpunit": "^9.5.10",
+        "spatie/laravel-ignition": "^1.0"
     },
     },
     "config": {
     "config": {
         "optimize-autoloader": true,
         "optimize-autoloader": true,
@@ -62,7 +58,7 @@
         "post-autoload-dump": [
         "post-autoload-dump": [
             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
             "@php artisan package:discover --ansi",
             "@php artisan package:discover --ansi",
-            "@php artisan version:absorb --ansi"
+            "@php artisan anonaddy:update-app-version"
         ],
         ],
         "post-root-package-install": [
         "post-root-package-install": [
             "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
             "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 213 - 423
composer.lock


+ 25 - 38
config/app.php

@@ -1,5 +1,7 @@
 <?php
 <?php
 
 
+use Illuminate\Support\Facades\Facade;
+
 return [
 return [
 
 
     /*
     /*
@@ -39,7 +41,7 @@ return [
     |
     |
     */
     */
 
 
-    'debug' => env('APP_DEBUG', false),
+    'debug' => (bool) env('APP_DEBUG', false),
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
@@ -54,7 +56,7 @@ return [
 
 
     'url' => env('APP_URL', 'http://localhost'),
     'url' => env('APP_URL', 'http://localhost'),
 
 
-    'asset_url' => env('ASSET_URL', null),
+    'asset_url' => env('ASSET_URL'),
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
@@ -123,6 +125,24 @@ return [
 
 
     'cipher' => 'AES-256-CBC',
     'cipher' => 'AES-256-CBC',
 
 
+    /*
+    |--------------------------------------------------------------------------
+    | Maintenance Mode Driver
+    |--------------------------------------------------------------------------
+    |
+    | These configuration options determine the driver used to determine and
+    | manage Laravel's "maintenance mode" status. The "cache" driver will
+    | allow maintenance mode to be controlled across multiple machines.
+    |
+    | Supported drivers: "file", "cache"
+    |
+    */
+
+    'maintenance' => [
+        'driver' => 'file',
+        // 'store'  => 'redis',
+    ],
+
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     | Autoloaded Service Providers
     | Autoloaded Service Providers
@@ -190,41 +210,8 @@ return [
     |
     |
     */
     */
 
 
-    'aliases' => [
-
-        'App' => Illuminate\Support\Facades\App::class,
-        'Artisan' => Illuminate\Support\Facades\Artisan::class,
-        'Auth' => Illuminate\Support\Facades\Auth::class,
-        'Blade' => Illuminate\Support\Facades\Blade::class,
-        'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
-        'Bus' => Illuminate\Support\Facades\Bus::class,
-        'Cache' => Illuminate\Support\Facades\Cache::class,
-        'Config' => Illuminate\Support\Facades\Config::class,
-        'Cookie' => Illuminate\Support\Facades\Cookie::class,
-        'Crypt' => Illuminate\Support\Facades\Crypt::class,
-        'DB' => Illuminate\Support\Facades\DB::class,
-        'Eloquent' => Illuminate\Database\Eloquent\Model::class,
-        'Event' => Illuminate\Support\Facades\Event::class,
-        'File' => Illuminate\Support\Facades\File::class,
-        'Gate' => Illuminate\Support\Facades\Gate::class,
-        'Hash' => Illuminate\Support\Facades\Hash::class,
-        'Lang' => Illuminate\Support\Facades\Lang::class,
-        'Log' => Illuminate\Support\Facades\Log::class,
-        'Mail' => Illuminate\Support\Facades\Mail::class,
-        'Notification' => Illuminate\Support\Facades\Notification::class,
-        'Password' => Illuminate\Support\Facades\Password::class,
-        'Queue' => Illuminate\Support\Facades\Queue::class,
-        'Redirect' => Illuminate\Support\Facades\Redirect::class,
-        'Request' => Illuminate\Support\Facades\Request::class,
-        'Response' => Illuminate\Support\Facades\Response::class,
-        'Route' => Illuminate\Support\Facades\Route::class,
-        'Schema' => Illuminate\Support\Facades\Schema::class,
-        'Session' => Illuminate\Support\Facades\Session::class,
-        'Storage' => Illuminate\Support\Facades\Storage::class,
-        'URL' => Illuminate\Support\Facades\URL::class,
-        'Validator' => Illuminate\Support\Facades\Validator::class,
-        'View' => Illuminate\Support\Facades\View::class,
-
-    ],
+    'aliases' => Facade::defaultAliases()->merge([
+        // 'ExampleClass' => App\Example\ExampleClass::class,
+    ])->toArray(),
 
 
 ];
 ];

+ 10 - 2
config/broadcasting.php

@@ -11,7 +11,7 @@ return [
     | framework when an event needs to be broadcast. You may set this to
     | framework when an event needs to be broadcast. You may set this to
     | any of the connections defined in the "connections" array below.
     | any of the connections defined in the "connections" array below.
     |
     |
-    | Supported: "pusher", "redis", "log", "null"
+    | Supported: "pusher", "ably", "redis", "log", "null"
     |
     |
     */
     */
 
 
@@ -37,8 +37,16 @@ return [
             'app_id' => env('PUSHER_APP_ID'),
             'app_id' => env('PUSHER_APP_ID'),
             'options' => [
             'options' => [
                 'cluster' => env('PUSHER_APP_CLUSTER'),
                 'cluster' => env('PUSHER_APP_CLUSTER'),
-                'encrypted' => true,
+                'useTLS' => true,
             ],
             ],
+            'client_options' => [
+                // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
+            ],
+        ],
+
+        'ably' => [
+            'driver' => 'ably',
+            'key' => env('ABLY_KEY'),
         ],
         ],
 
 
         'redis' => [
         'redis' => [

+ 3 - 3
config/cache.php

@@ -82,9 +82,9 @@ return [
     | Cache Key Prefix
     | Cache Key Prefix
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     |
     |
-    | When utilizing a RAM based store such as APC or Memcached, there might
-    | be other applications utilizing the same cache. So, we'll specify a
-    | value to get prefixed to all our keys so we can avoid collisions.
+    | When utilizing the APC, database, memcached, Redis, or DynamoDB cache
+    | stores there might be other applications using the same cache. For
+    | that reason, you may prefix every cache key to avoid collisions.
     |
     |
     */
     */
 
 

+ 13 - 1
config/database.php

@@ -1,5 +1,7 @@
 <?php
 <?php
 
 
+use Illuminate\Support\Str;
+
 return [
 return [
 
 
     /*
     /*
@@ -54,6 +56,9 @@ return [
             'prefix_indexes' => true,
             'prefix_indexes' => true,
             'strict' => true,
             'strict' => true,
             'engine' => null,
             'engine' => null,
+            'options' => extension_loaded('pdo_mysql') ? array_filter([
+                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
+            ]) : [],
         ],
         ],
 
 
         'pgsql' => [
         'pgsql' => [
@@ -66,7 +71,7 @@ return [
             'charset' => 'utf8',
             'charset' => 'utf8',
             'prefix' => '',
             'prefix' => '',
             'prefix_indexes' => true,
             'prefix_indexes' => true,
-            'schema' => 'public',
+            'search_path' => 'public',
             'sslmode' => 'prefer',
             'sslmode' => 'prefer',
         ],
         ],
 
 
@@ -80,6 +85,8 @@ return [
             'charset' => 'utf8',
             'charset' => 'utf8',
             'prefix' => '',
             'prefix' => '',
             'prefix_indexes' => true,
             'prefix_indexes' => true,
+            // 'encrypt' => env('DB_ENCRYPT', 'yes'),
+            // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
         ],
         ],
 
 
     ],
     ],
@@ -112,6 +119,11 @@ return [
 
 
         'client' => env('REDIS_CLIENT', 'phpredis'),
         'client' => env('REDIS_CLIENT', 'phpredis'),
 
 
+        'options' => [
+            'cluster' => env('REDIS_CLUSTER', 'redis'),
+            'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
+        ],
+
         'default' => [
         'default' => [
             'host' => env('REDIS_HOST', '127.0.0.1'),
             'host' => env('REDIS_HOST', '127.0.0.1'),
             'password' => env('REDIS_PASSWORD', null),
             'password' => env('REDIS_PASSWORD', null),

+ 5 - 2
config/filesystems.php

@@ -13,7 +13,7 @@ return [
     |
     |
     */
     */
 
 
-    'default' => env('FILESYSTEM_DRIVER', 'local'),
+    'default' => env('FILESYSTEM_DISK', 'local'),
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
@@ -35,7 +35,7 @@ return [
     |
     |
     | Here you may configure as many filesystem "disks" as you wish, and you
     | Here you may configure as many filesystem "disks" as you wish, and you
     | may even configure multiple disks of the same driver. Defaults have
     | may even configure multiple disks of the same driver. Defaults have
-    | been setup for each driver as an example of the required options.
+    | been set up for each driver as an example of the required values.
     |
     |
     | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace"
     | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace"
     |
     |
@@ -46,6 +46,7 @@ return [
         'local' => [
         'local' => [
             'driver' => 'local',
             'driver' => 'local',
             'root' => storage_path('app'),
             'root' => storage_path('app'),
+            'throw' => false,
         ],
         ],
 
 
         'public' => [
         'public' => [
@@ -53,6 +54,7 @@ return [
             'root' => storage_path('app/public'),
             'root' => storage_path('app/public'),
             'url' => env('APP_URL').'/storage',
             'url' => env('APP_URL').'/storage',
             'visibility' => 'public',
             'visibility' => 'public',
+            'throw' => false,
         ],
         ],
 
 
         's3' => [
         's3' => [
@@ -62,6 +64,7 @@ return [
             'region' => env('AWS_DEFAULT_REGION'),
             'region' => env('AWS_DEFAULT_REGION'),
             'bucket' => env('AWS_BUCKET'),
             'bucket' => env('AWS_BUCKET'),
             'url' => env('AWS_URL'),
             'url' => env('AWS_URL'),
+            'throw' => false,
         ],
         ],
 
 
     ],
     ],

+ 29 - 2
config/logging.php

@@ -1,5 +1,6 @@
 <?php
 <?php
 
 
+use Monolog\Handler\NullHandler;
 use Monolog\Handler\StreamHandler;
 use Monolog\Handler\StreamHandler;
 use Monolog\Handler\SyslogUdpHandler;
 use Monolog\Handler\SyslogUdpHandler;
 
 
@@ -18,6 +19,22 @@ return [
 
 
     'default' => env('LOG_CHANNEL', 'stack'),
     'default' => env('LOG_CHANNEL', 'stack'),
 
 
+    /*
+    |--------------------------------------------------------------------------
+    | Deprecations Log Channel
+    |--------------------------------------------------------------------------
+    |
+    | This option controls the log channel that should be used to log warnings
+    | regarding deprecated PHP and library features. This allows you to get
+    | your application ready for upcoming major versions of dependencies.
+    |
+    */
+
+    'deprecations' => [
+        'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
+        'trace' => false,
+    ],
+
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     | Log Channels
     | Log Channels
@@ -63,11 +80,12 @@ return [
 
 
         'papertrail' => [
         'papertrail' => [
             'driver' => 'monolog',
             'driver' => 'monolog',
-            'level' => 'debug',
-            'handler' => SyslogUdpHandler::class,
+            'level' => env('LOG_LEVEL', 'debug'),
+            'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
             'handler_with' => [
             'handler_with' => [
                 'host' => env('PAPERTRAIL_URL'),
                 'host' => env('PAPERTRAIL_URL'),
                 'port' => env('PAPERTRAIL_PORT'),
                 'port' => env('PAPERTRAIL_PORT'),
+                'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
             ],
             ],
         ],
         ],
 
 
@@ -89,6 +107,15 @@ return [
             'driver' => 'errorlog',
             'driver' => 'errorlog',
             'level' => 'debug',
             'level' => 'debug',
         ],
         ],
+
+        'null' => [
+            'driver' => 'monolog',
+            'handler' => NullHandler::class,
+        ],
+
+        'emergency' => [
+            'path' => storage_path('logs/laravel.log'),
+        ],
     ],
     ],
 
 
 ];
 ];

+ 61 - 79
config/mail.php

@@ -4,45 +4,82 @@ return [
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
-    | Mail Driver
+    | Default Mailer
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     |
     |
-    | Laravel supports both SMTP and PHP's "mail" function as drivers for the
-    | sending of e-mail. You may specify which one you're using throughout
-    | your application here. By default, Laravel is setup for SMTP mail.
-    |
-    | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses",
-    |            "sparkpost", "log", "array"
+    | This option controls the default mailer that is used to send any email
+    | messages sent by your application. Alternative mailers may be setup
+    | and used as needed; however, this mailer will be used by default.
     |
     |
     */
     */
 
 
-    'driver' => env('MAIL_DRIVER', 'smtp'),
+    'default' => env('MAIL_MAILER', 'smtp'),
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
-    | SMTP Host Address
+    | Mailer Configurations
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     |
     |
-    | Here you may provide the host address of the SMTP server used by your
-    | applications. A default option is provided that is compatible with
-    | the Mailgun mail service which will provide reliable deliveries.
+    | Here you may configure all of the mailers used by your application plus
+    | their respective settings. Several examples have been configured for
+    | you and you are free to add your own as your application requires.
     |
     |
-    */
-
-    'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
-
-    /*
-    |--------------------------------------------------------------------------
-    | SMTP Host Port
-    |--------------------------------------------------------------------------
+    | Laravel supports a variety of mail "transport" drivers to be used while
+    | sending an e-mail. You will specify which one you are using for your
+    | mailers below. You are free to add additional mailers as required.
     |
     |
-    | This is the SMTP port used by your application to deliver e-mails to
-    | users of the application. Like the host we have set this value to
-    | stay compatible with the Mailgun e-mail application by default.
+    | Supported: "smtp", "sendmail", "mailgun", "ses",
+    |            "postmark", "log", "array", "failover"
     |
     |
     */
     */
 
 
-    'port' => env('MAIL_PORT', 587),
+    'mailers' => [
+        'smtp' => [
+            'transport' => 'smtp',
+            'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
+            'port' => env('MAIL_PORT', 587),
+            'encryption' => env('MAIL_ENCRYPTION', 'tls'),
+            'username' => env('MAIL_USERNAME'),
+            'password' => env('MAIL_PASSWORD'),
+            'timeout' => null,
+            'local_domain' => env('MAIL_EHLO_DOMAIN'),
+            'verify_peer' => env('MAIL_VERIFY_PEER', false),
+        ],
+
+        'ses' => [
+            'transport' => 'ses',
+        ],
+
+        'mailgun' => [
+            'transport' => 'mailgun',
+        ],
+
+        'postmark' => [
+            'transport' => 'postmark',
+        ],
+
+        'sendmail' => [
+            'transport' => 'sendmail',
+            'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
+        ],
+
+        'log' => [
+            'transport' => 'log',
+            'channel' => env('MAIL_LOG_CHANNEL'),
+        ],
+
+        'array' => [
+            'transport' => 'array',
+        ],
+
+        'failover' => [
+            'transport' => 'failover',
+            'mailers' => [
+                'smtp',
+                'log',
+            ],
+        ],
+    ],
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
@@ -60,47 +97,6 @@ return [
         'name' => env('MAIL_FROM_NAME', 'Example'),
         'name' => env('MAIL_FROM_NAME', 'Example'),
     ],
     ],
 
 
-    /*
-    |--------------------------------------------------------------------------
-    | E-Mail Encryption Protocol
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify the encryption protocol that should be used when
-    | the application send e-mail messages. A sensible default using the
-    | transport layer security protocol should provide great security.
-    |
-    */
-
-    'encryption' => env('MAIL_ENCRYPTION', 'tls'),
-
-    /*
-    |--------------------------------------------------------------------------
-    | SMTP Server Username
-    |--------------------------------------------------------------------------
-    |
-    | If your SMTP server requires a username for authentication, you should
-    | set it here. This will get used to authenticate with your server on
-    | connection. You may also set the "password" value below this one.
-    |
-    */
-
-    'username' => env('MAIL_USERNAME'),
-
-    'password' => env('MAIL_PASSWORD'),
-
-    /*
-    |--------------------------------------------------------------------------
-    | Sendmail System Path
-    |--------------------------------------------------------------------------
-    |
-    | When using the "sendmail" driver to send e-mails, we will need to know
-    | the path to where Sendmail lives on this server. A default path has
-    | been provided here, which will work well on most of your systems.
-    |
-    */
-
-    'sendmail' => '/usr/sbin/sendmail -bs',
-
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     | Markdown Mail Settings
     | Markdown Mail Settings
@@ -119,18 +115,4 @@ return [
             resource_path('views/vendor/mail'),
             resource_path('views/vendor/mail'),
         ],
         ],
     ],
     ],
-
-    /*
-    |--------------------------------------------------------------------------
-    | Log Channel
-    |--------------------------------------------------------------------------
-    |
-    | If you are using the "log" driver, you may specify the logging channel
-    | if you prefer to keep mail messages separate from other log entries
-    | for simpler reading. Otherwise, the default channel will be used.
-    |
-    */
-
-    'log_channel' => env('MAIL_LOG_CHANNEL'),
-
 ];
 ];

+ 1 - 0
config/services.php

@@ -18,6 +18,7 @@ return [
         'domain' => env('MAILGUN_DOMAIN'),
         'domain' => env('MAILGUN_DOMAIN'),
         'secret' => env('MAILGUN_SECRET'),
         'secret' => env('MAILGUN_SECRET'),
         'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
         'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
+        'scheme' => 'https',
     ],
     ],
 
 
     'ses' => [
     'ses' => [

+ 0 - 60
config/version.yml

@@ -1,60 +0,0 @@
-mode: absorb
-blade-directive: version
-current:
-  label: v
-  major: 0
-  minor: 11
-  patch: 2
-  prerelease: 5-ge800fa7
-  buildmetadata: ''
-  commit: e800fa
-  timestamp:
-    year: 2020
-    month: 10
-    day: 16
-    hour: 11
-    minute: 8
-    second: 24
-    timezone: UTC
-commit:
-  mode: absorb
-  length: 6
-  increment-by: 1
-git:
-  from: local
-  commit:
-    local: 'git rev-parse --verify HEAD'
-    remote: 'git ls-remote {$repository}'
-  branch: refs/heads/master
-  repository: ''
-  version:
-    local: 'git describe'
-    remote: 'git ls-remote {$repository} | grep tags/ | grep -v {} | cut -d / -f 3 | sort --version-sort | tail -1'
-    matcher: '/^(?P<label>[v|V]*[er]*[sion]*)[\.|\s]*(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/'
-  timestamp:
-    local: 'git show -s --format=%ci'
-    remote: 'git show -s --format=%ci origin/master'
-format:
-  regex:
-    optional_bracket: '\[(?P<prefix>.*?)(?P<spaces>\s*)(?P<delimiter>\?\=)(?P<optional>.*?)\]'
-  label: '{$label}'
-  major: '{$major}'
-  minor: '{$minor}'
-  patch: '{$patch}'
-  prerelease: '{$prerelease}'
-  buildmetadata: '{$buildmetadata}'
-  commit: '{$commit}'
-  release: 'v{$major}.{$minor}.{$patch}'
-  version: '{$major}.{$minor}.{$patch}'
-  version-only: 'version {$major}.{$minor}.{$patch}'
-  full: '{$version-only}[.?={$prerelease}][+?={$buildmetadata}] (commit {$commit})'
-  compact: 'v{$major}.{$minor}.{$patch}-{$commit}'
-  timestamp-year: '{$timestamp.year}'
-  timestamp-month: '{$timestamp.month}'
-  timestamp-day: '{$timestamp.day}'
-  timestamp-hour: '{$timestamp.hour}'
-  timestamp-minute: '{$timestamp.minute}'
-  timestamp-second: '{$timestamp.second}'
-  timestamp-timezone: '{$timestamp.timezone}'
-  timestamp-datetime: '{$timestamp.year}-{$timestamp.month}-{$timestamp.day} {$timestamp.hour}:{$timestamp.minute}:{$timestamp.second}'
-  timestamp-full: '{$timestamp.year}-{$timestamp.month}-{$timestamp.day} {$timestamp.hour}:{$timestamp.minute}:{$timestamp.second} {$timestamp.timezone}'

+ 69 - 12
config/webauthn.php

@@ -1,5 +1,7 @@
 <?php
 <?php
 
 
+use App\Models\WebauthnKey;
+
 return [
 return [
 
 
     /*
     /*
@@ -15,31 +17,84 @@ return [
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
-    | Route Middleware
+    | Webauthn Guard
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     |
     |
-    | These middleware will be assigned to Webauthn routes, giving you
-    | the chance to add your own middleware to this list or change any of
-    | the existing middleware. Or, you can simply stick with this list.
+    | Here you may specify which authentication guard Webauthn will use while
+    | authenticating users. This value should correspond with one of your
+    | guards that is already present in your "auth" configuration file.
     |
     |
     */
     */
 
 
-    'middleware' => [
-        'web',
-        'auth',
-    ],
+    'guard' => 'web',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Username / Email
+    |--------------------------------------------------------------------------
+    |
+    | This value defines which model attribute should be considered as your
+    | application's "username" field. Typically, this might be the email
+    | address of the users but you are free to change this value here.
+    |
+    */
+
+    'username' => 'email',
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
-    | Prefix path
+    | Webauthn Routes Prefix / Subdomain
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     |
     |
-    | The uri prefix for all webauthn requests.
+    | Here you may specify which prefix Webauthn will assign to all the routes
+    | that it registers with the application. If necessary, you may change
+    | subdomain under which all of the Webauthn routes will be available.
     |
     |
     */
     */
 
 
     'prefix' => 'webauthn',
     'prefix' => 'webauthn',
 
 
+    'domain' => null,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Webauthn Routes Middleware
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify which middleware Webauthn will assign to the routes
+    | that it registers with the application. If necessary, you may change
+    | these middleware but typically this provided default is preferred.
+    |
+    */
+
+    'middleware' => ['web'],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Webauthn key model
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify the model used to create Webauthn keys.
+    |
+    */
+
+    'model' => WebauthnKey::class,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Rate Limiting
+    |--------------------------------------------------------------------------
+    |
+    | By default, Laravel Webauthn will throttle logins to five requests per
+    | minute for every email and IP address combination. However, if you would
+    | like to specify a custom rate limiter to call then you may specify it here.
+    |
+    */
+
+    'limiters' => [
+        'login' => null,
+    ],
+
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
     | Redirect routes
     | Redirect routes
@@ -67,6 +122,8 @@ return [
     | - authenticate: when a user login, and has to validate Webauthn 2nd factor.
     | - authenticate: when a user login, and has to validate Webauthn 2nd factor.
     | - register: when a user request to create a Webauthn key.
     | - register: when a user request to create a Webauthn key.
     |
     |
+    | If the views are empty or null, then the route will not be registered.
+    |
     */
     */
 
 
     'views' => [
     'views' => [
@@ -83,7 +140,7 @@ return [
     |
     |
     */
     */
 
 
-    'sessionName' => 'webauthn_auth',
+    'session_name' => 'webauthn_auth',
 
 
     /*
     /*
     |--------------------------------------------------------------------------
     |--------------------------------------------------------------------------
@@ -226,6 +283,6 @@ return [
     |
     |
     */
     */
 
 
-    'userless' => 'null',
+    'userless' => null,
 
 
 ];
 ];

+ 31 - 0
database/migrations/2022_06_29_103709_update_email_type_in_failed_deliveries_table.php

@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class () extends Migration {
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('failed_deliveries', function (Blueprint $table) {
+            $table->string('email_type', 5)->nullable()->change();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('failed_deliveries', function (Blueprint $table) {
+            $table->string('email_type', 3)->nullable()->change();
+        });
+    }
+};

+ 152 - 110
package-lock.json

@@ -102,12 +102,12 @@
             }
             }
         },
         },
         "node_modules/@babel/generator": {
         "node_modules/@babel/generator": {
-            "version": "7.18.6",
-            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.6.tgz",
-            "integrity": "sha512-AIwwoOS8axIC5MZbhNHRLKi3D+DMpvDf9XUcu3pIVAfOHFT45f4AoDAltRbHIQomCipkCZxrNkfpOEHhJz/VKw==",
+            "version": "7.18.7",
+            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz",
+            "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==",
             "dependencies": {
             "dependencies": {
-                "@babel/types": "^7.18.6",
-                "@jridgewell/gen-mapping": "^0.3.0",
+                "@babel/types": "^7.18.7",
+                "@jridgewell/gen-mapping": "^0.3.2",
                 "jsesc": "^2.5.1"
                 "jsesc": "^2.5.1"
             },
             },
             "engines": {
             "engines": {
@@ -1625,9 +1625,9 @@
             }
             }
         },
         },
         "node_modules/@babel/types": {
         "node_modules/@babel/types": {
-            "version": "7.18.6",
-            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.6.tgz",
-            "integrity": "sha512-NdBNzPDwed30fZdDQtVR7ZgaO4UKjuaQFH9VArS+HMnurlOY0JWN+4ROlu/iapMFwjRQU4pOG4StZfDmulEwGA==",
+            "version": "7.18.7",
+            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz",
+            "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==",
             "dependencies": {
             "dependencies": {
                 "@babel/helper-validator-identifier": "^7.18.6",
                 "@babel/helper-validator-identifier": "^7.18.6",
                 "to-fast-properties": "^2.0.0"
                 "to-fast-properties": "^2.0.0"
@@ -1666,9 +1666,9 @@
             }
             }
         },
         },
         "node_modules/@jridgewell/resolve-uri": {
         "node_modules/@jridgewell/resolve-uri": {
-            "version": "3.0.8",
-            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz",
-            "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==",
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+            "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
             "engines": {
             "engines": {
                 "node": ">=6.0.0"
                 "node": ">=6.0.0"
             }
             }
@@ -1852,18 +1852,18 @@
             }
             }
         },
         },
         "node_modules/@types/eslint": {
         "node_modules/@types/eslint": {
-            "version": "8.4.3",
-            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.3.tgz",
-            "integrity": "sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw==",
+            "version": "8.4.5",
+            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
+            "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==",
             "dependencies": {
             "dependencies": {
                 "@types/estree": "*",
                 "@types/estree": "*",
                 "@types/json-schema": "*"
                 "@types/json-schema": "*"
             }
             }
         },
         },
         "node_modules/@types/eslint-scope": {
         "node_modules/@types/eslint-scope": {
-            "version": "3.7.3",
-            "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz",
-            "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==",
+            "version": "3.7.4",
+            "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz",
+            "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==",
             "dependencies": {
             "dependencies": {
                 "@types/eslint": "*",
                 "@types/eslint": "*",
                 "@types/estree": "*"
                 "@types/estree": "*"
@@ -1969,9 +1969,9 @@
             "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
             "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
         },
         },
         "node_modules/@types/node": {
         "node_modules/@types/node": {
-            "version": "18.0.0",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
-            "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA=="
+            "version": "18.0.3",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz",
+            "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ=="
         },
         },
         "node_modules/@types/parse-json": {
         "node_modules/@types/parse-json": {
             "version": "4.0.0",
             "version": "4.0.0",
@@ -2031,6 +2031,16 @@
                 "@types/node": "*"
                 "@types/node": "*"
             }
             }
         },
         },
+        "node_modules/@vue/compiler-sfc": {
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.3.tgz",
+            "integrity": "sha512-/9SO6KeR1ljo+j4Tdkl9zsFG2yY4NQ9p3GKdPVCU99bmkNkTW8g9Tq8SMEvhOfo+RhBC0PdDAOmibl+N37f5YA==",
+            "dependencies": {
+                "@babel/parser": "^7.18.4",
+                "postcss": "^8.4.14",
+                "source-map": "^0.6.1"
+            }
+        },
         "node_modules/@vue/component-compiler-utils": {
         "node_modules/@vue/component-compiler-utils": {
             "version": "3.3.0",
             "version": "3.3.0",
             "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
             "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
@@ -2858,9 +2868,9 @@
             }
             }
         },
         },
         "node_modules/browserslist": {
         "node_modules/browserslist": {
-            "version": "4.21.0",
-            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.0.tgz",
-            "integrity": "sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA==",
+            "version": "4.21.1",
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.1.tgz",
+            "integrity": "sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==",
             "funding": [
             "funding": [
                 {
                 {
                     "type": "opencollective",
                     "type": "opencollective",
@@ -2872,10 +2882,10 @@
                 }
                 }
             ],
             ],
             "dependencies": {
             "dependencies": {
-                "caniuse-lite": "^1.0.30001358",
-                "electron-to-chromium": "^1.4.164",
+                "caniuse-lite": "^1.0.30001359",
+                "electron-to-chromium": "^1.4.172",
                 "node-releases": "^2.0.5",
                 "node-releases": "^2.0.5",
-                "update-browserslist-db": "^1.0.0"
+                "update-browserslist-db": "^1.0.4"
             },
             },
             "bin": {
             "bin": {
                 "browserslist": "cli.js"
                 "browserslist": "cli.js"
@@ -2966,9 +2976,9 @@
             }
             }
         },
         },
         "node_modules/caniuse-lite": {
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001359",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz",
-            "integrity": "sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw==",
+            "version": "1.0.30001363",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001363.tgz",
+            "integrity": "sha512-HpQhpzTGGPVMnCjIomjt+jvyUu8vNFo3TaDiZ/RcoTrlOq/5+tC8zHdsbgFB6MxmaY+jCpsH09aD80Bb4Ow3Sg==",
             "funding": [
             "funding": [
                 {
                 {
                     "type": "opencollective",
                     "type": "opencollective",
@@ -3182,9 +3192,9 @@
             }
             }
         },
         },
         "node_modules/collect.js": {
         "node_modules/collect.js": {
-            "version": "4.34.0",
-            "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.34.0.tgz",
-            "integrity": "sha512-WoXbKDghKWb1lnN1ScBs/MR7BvOpyE5kI0Q9+k8rFtShLFpgjosYE5YplGKxg/DDSkPXgWzgdNZAEnFUffw1xg=="
+            "version": "4.34.3",
+            "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.34.3.tgz",
+            "integrity": "sha512-aFr67xDazPwthsGm729mnClgNuh15JEagU6McKBKqxuHOkWL7vMFzGbhsXDdPZ+H6ia5QKIMGYuGOMENBHnVpg=="
         },
         },
         "node_modules/color-convert": {
         "node_modules/color-convert": {
             "version": "2.0.1",
             "version": "2.0.1",
@@ -3291,9 +3301,9 @@
             "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
             "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
         },
         },
         "node_modules/connect-history-api-fallback": {
         "node_modules/connect-history-api-fallback": {
-            "version": "1.6.0",
-            "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
-            "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz",
+            "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==",
             "engines": {
             "engines": {
                 "node": ">=0.8"
                 "node": ">=0.8"
             }
             }
@@ -3705,6 +3715,11 @@
                 "node": ">=8.0.0"
                 "node": ">=8.0.0"
             }
             }
         },
         },
+        "node_modules/csstype": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+            "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
+        },
         "node_modules/date-fns": {
         "node_modules/date-fns": {
             "version": "2.28.0",
             "version": "2.28.0",
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
@@ -4003,9 +4018,9 @@
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
         },
         },
         "node_modules/electron-to-chromium": {
         "node_modules/electron-to-chromium": {
-            "version": "1.4.172",
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.172.tgz",
-            "integrity": "sha512-yDoFfTJnqBAB6hSiPvzmsBJSrjOXJtHSJoqJdI/zSIh7DYupYnIOHt/bbPw/WE31BJjNTybDdNAs21gCMnTh0Q=="
+            "version": "1.4.182",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.182.tgz",
+            "integrity": "sha512-OpEjTADzGoXABjqobGhpy0D2YsTncAax7IkER68ycc4adaq0dqEG9//9aenKPy7BGA90bqQdLac0dPp6uMkcSg=="
         },
         },
         "node_modules/elliptic": {
         "node_modules/elliptic": {
             "version": "6.5.4",
             "version": "6.5.4",
@@ -4048,9 +4063,9 @@
             }
             }
         },
         },
         "node_modules/enhanced-resolve": {
         "node_modules/enhanced-resolve": {
-            "version": "5.9.3",
-            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
-            "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==",
+            "version": "5.10.0",
+            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
+            "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
             "dependencies": {
             "dependencies": {
                 "graceful-fs": "^4.2.4",
                 "graceful-fs": "^4.2.4",
                 "tapable": "^2.2.0"
                 "tapable": "^2.2.0"
@@ -8622,9 +8637,13 @@
             "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
             "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
         },
         },
         "node_modules/vue": {
         "node_modules/vue": {
-            "version": "2.6.14",
-            "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
-            "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.3.tgz",
+            "integrity": "sha512-7MTirXG7LYJi3r2jeYy7EiXIhEE85uP3kBwSxqwDsvIfy/l7+qR4U6ajw8ji1KoP+wYYg7ZY28TBTf3P3Fa4Nw==",
+            "dependencies": {
+                "@vue/compiler-sfc": "2.7.3",
+                "csstype": "^3.1.0"
+            }
         },
         },
         "node_modules/vue-good-table": {
         "node_modules/vue-good-table": {
             "version": "2.21.11",
             "version": "2.21.11",
@@ -8641,9 +8660,9 @@
             "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
             "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
         },
         },
         "node_modules/vue-loader": {
         "node_modules/vue-loader": {
-            "version": "15.9.8",
-            "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz",
-            "integrity": "sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==",
+            "version": "15.10.0",
+            "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz",
+            "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==",
             "dependencies": {
             "dependencies": {
                 "@vue/component-compiler-utils": "^3.1.0",
                 "@vue/component-compiler-utils": "^3.1.0",
                 "hash-sum": "^1.0.2",
                 "hash-sum": "^1.0.2",
@@ -8739,12 +8758,12 @@
             }
             }
         },
         },
         "node_modules/vue-template-compiler": {
         "node_modules/vue-template-compiler": {
-            "version": "2.6.14",
-            "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
-            "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==",
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.3.tgz",
+            "integrity": "sha512-QKcV4vj9akZ2zSD1+F7tci3aXpRe5DXU3TZ/ZWJPE9gInXD5UoV+Q2PrCaplj4IGIK8JXRHYaSvOXkOn7tg9Og==",
             "dependencies": {
             "dependencies": {
                 "de-indent": "^1.0.2",
                 "de-indent": "^1.0.2",
-                "he": "^1.1.0"
+                "he": "^1.2.0"
             }
             }
         },
         },
         "node_modules/vue-template-es2015-compiler": {
         "node_modules/vue-template-es2015-compiler": {
@@ -8944,9 +8963,9 @@
             }
             }
         },
         },
         "node_modules/webpack-dev-server": {
         "node_modules/webpack-dev-server": {
-            "version": "4.9.2",
-            "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.2.tgz",
-            "integrity": "sha512-H95Ns95dP24ZsEzO6G9iT+PNw4Q7ltll1GfJHV4fKphuHWgKFzGHWi4alTlTnpk1SPPk41X+l2RB7rLfIhnB9Q==",
+            "version": "4.9.3",
+            "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz",
+            "integrity": "sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==",
             "dependencies": {
             "dependencies": {
                 "@types/bonjour": "^3.5.9",
                 "@types/bonjour": "^3.5.9",
                 "@types/connect-history-api-fallback": "^1.3.5",
                 "@types/connect-history-api-fallback": "^1.3.5",
@@ -8960,7 +8979,7 @@
                 "chokidar": "^3.5.3",
                 "chokidar": "^3.5.3",
                 "colorette": "^2.0.10",
                 "colorette": "^2.0.10",
                 "compression": "^1.7.4",
                 "compression": "^1.7.4",
-                "connect-history-api-fallback": "^1.6.0",
+                "connect-history-api-fallback": "^2.0.0",
                 "default-gateway": "^6.0.3",
                 "default-gateway": "^6.0.3",
                 "express": "^4.17.3",
                 "express": "^4.17.3",
                 "graceful-fs": "^4.2.6",
                 "graceful-fs": "^4.2.6",
@@ -8984,6 +9003,10 @@
             "engines": {
             "engines": {
                 "node": ">= 12.13.0"
                 "node": ">= 12.13.0"
             },
             },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/webpack"
+            },
             "peerDependencies": {
             "peerDependencies": {
                 "webpack": "^4.37.0 || ^5.0.0"
                 "webpack": "^4.37.0 || ^5.0.0"
             },
             },
@@ -9330,12 +9353,12 @@
             }
             }
         },
         },
         "@babel/generator": {
         "@babel/generator": {
-            "version": "7.18.6",
-            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.6.tgz",
-            "integrity": "sha512-AIwwoOS8axIC5MZbhNHRLKi3D+DMpvDf9XUcu3pIVAfOHFT45f4AoDAltRbHIQomCipkCZxrNkfpOEHhJz/VKw==",
+            "version": "7.18.7",
+            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz",
+            "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==",
             "requires": {
             "requires": {
-                "@babel/types": "^7.18.6",
-                "@jridgewell/gen-mapping": "^0.3.0",
+                "@babel/types": "^7.18.7",
+                "@jridgewell/gen-mapping": "^0.3.2",
                 "jsesc": "^2.5.1"
                 "jsesc": "^2.5.1"
             },
             },
             "dependencies": {
             "dependencies": {
@@ -10367,9 +10390,9 @@
             }
             }
         },
         },
         "@babel/types": {
         "@babel/types": {
-            "version": "7.18.6",
-            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.6.tgz",
-            "integrity": "sha512-NdBNzPDwed30fZdDQtVR7ZgaO4UKjuaQFH9VArS+HMnurlOY0JWN+4ROlu/iapMFwjRQU4pOG4StZfDmulEwGA==",
+            "version": "7.18.7",
+            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz",
+            "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==",
             "requires": {
             "requires": {
                 "@babel/helper-validator-identifier": "^7.18.6",
                 "@babel/helper-validator-identifier": "^7.18.6",
                 "to-fast-properties": "^2.0.0"
                 "to-fast-properties": "^2.0.0"
@@ -10396,9 +10419,9 @@
             }
             }
         },
         },
         "@jridgewell/resolve-uri": {
         "@jridgewell/resolve-uri": {
-            "version": "3.0.8",
-            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz",
-            "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w=="
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+            "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
         },
         },
         "@jridgewell/set-array": {
         "@jridgewell/set-array": {
             "version": "1.1.2",
             "version": "1.1.2",
@@ -10559,18 +10582,18 @@
             }
             }
         },
         },
         "@types/eslint": {
         "@types/eslint": {
-            "version": "8.4.3",
-            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.3.tgz",
-            "integrity": "sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw==",
+            "version": "8.4.5",
+            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
+            "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==",
             "requires": {
             "requires": {
                 "@types/estree": "*",
                 "@types/estree": "*",
                 "@types/json-schema": "*"
                 "@types/json-schema": "*"
             }
             }
         },
         },
         "@types/eslint-scope": {
         "@types/eslint-scope": {
-            "version": "3.7.3",
-            "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz",
-            "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==",
+            "version": "3.7.4",
+            "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz",
+            "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==",
             "requires": {
             "requires": {
                 "@types/eslint": "*",
                 "@types/eslint": "*",
                 "@types/estree": "*"
                 "@types/estree": "*"
@@ -10676,9 +10699,9 @@
             "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
             "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
         },
         },
         "@types/node": {
         "@types/node": {
-            "version": "18.0.0",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
-            "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA=="
+            "version": "18.0.3",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz",
+            "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ=="
         },
         },
         "@types/parse-json": {
         "@types/parse-json": {
             "version": "4.0.0",
             "version": "4.0.0",
@@ -10738,6 +10761,16 @@
                 "@types/node": "*"
                 "@types/node": "*"
             }
             }
         },
         },
+        "@vue/compiler-sfc": {
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.3.tgz",
+            "integrity": "sha512-/9SO6KeR1ljo+j4Tdkl9zsFG2yY4NQ9p3GKdPVCU99bmkNkTW8g9Tq8SMEvhOfo+RhBC0PdDAOmibl+N37f5YA==",
+            "requires": {
+                "@babel/parser": "^7.18.4",
+                "postcss": "^8.4.14",
+                "source-map": "^0.6.1"
+            }
+        },
         "@vue/component-compiler-utils": {
         "@vue/component-compiler-utils": {
             "version": "3.3.0",
             "version": "3.3.0",
             "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
             "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
@@ -11406,14 +11439,14 @@
             }
             }
         },
         },
         "browserslist": {
         "browserslist": {
-            "version": "4.21.0",
-            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.0.tgz",
-            "integrity": "sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA==",
+            "version": "4.21.1",
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.1.tgz",
+            "integrity": "sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==",
             "requires": {
             "requires": {
-                "caniuse-lite": "^1.0.30001358",
-                "electron-to-chromium": "^1.4.164",
+                "caniuse-lite": "^1.0.30001359",
+                "electron-to-chromium": "^1.4.172",
                 "node-releases": "^2.0.5",
                 "node-releases": "^2.0.5",
-                "update-browserslist-db": "^1.0.0"
+                "update-browserslist-db": "^1.0.4"
             }
             }
         },
         },
         "buffer": {
         "buffer": {
@@ -11486,9 +11519,9 @@
             }
             }
         },
         },
         "caniuse-lite": {
         "caniuse-lite": {
-            "version": "1.0.30001359",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz",
-            "integrity": "sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw=="
+            "version": "1.0.30001363",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001363.tgz",
+            "integrity": "sha512-HpQhpzTGGPVMnCjIomjt+jvyUu8vNFo3TaDiZ/RcoTrlOq/5+tC8zHdsbgFB6MxmaY+jCpsH09aD80Bb4Ow3Sg=="
         },
         },
         "chalk": {
         "chalk": {
             "version": "4.1.2",
             "version": "4.1.2",
@@ -11630,9 +11663,9 @@
             }
             }
         },
         },
         "collect.js": {
         "collect.js": {
-            "version": "4.34.0",
-            "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.34.0.tgz",
-            "integrity": "sha512-WoXbKDghKWb1lnN1ScBs/MR7BvOpyE5kI0Q9+k8rFtShLFpgjosYE5YplGKxg/DDSkPXgWzgdNZAEnFUffw1xg=="
+            "version": "4.34.3",
+            "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.34.3.tgz",
+            "integrity": "sha512-aFr67xDazPwthsGm729mnClgNuh15JEagU6McKBKqxuHOkWL7vMFzGbhsXDdPZ+H6ia5QKIMGYuGOMENBHnVpg=="
         },
         },
         "color-convert": {
         "color-convert": {
             "version": "2.0.1",
             "version": "2.0.1",
@@ -11725,9 +11758,9 @@
             "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
             "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
         },
         },
         "connect-history-api-fallback": {
         "connect-history-api-fallback": {
-            "version": "1.6.0",
-            "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
-            "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg=="
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz",
+            "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA=="
         },
         },
         "consola": {
         "consola": {
             "version": "2.15.3",
             "version": "2.15.3",
@@ -12027,6 +12060,11 @@
                 "css-tree": "^1.1.2"
                 "css-tree": "^1.1.2"
             }
             }
         },
         },
+        "csstype": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+            "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
+        },
         "date-fns": {
         "date-fns": {
             "version": "2.28.0",
             "version": "2.28.0",
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
@@ -12248,9 +12286,9 @@
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
         },
         },
         "electron-to-chromium": {
         "electron-to-chromium": {
-            "version": "1.4.172",
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.172.tgz",
-            "integrity": "sha512-yDoFfTJnqBAB6hSiPvzmsBJSrjOXJtHSJoqJdI/zSIh7DYupYnIOHt/bbPw/WE31BJjNTybDdNAs21gCMnTh0Q=="
+            "version": "1.4.182",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.182.tgz",
+            "integrity": "sha512-OpEjTADzGoXABjqobGhpy0D2YsTncAax7IkER68ycc4adaq0dqEG9//9aenKPy7BGA90bqQdLac0dPp6uMkcSg=="
         },
         },
         "elliptic": {
         "elliptic": {
             "version": "6.5.4",
             "version": "6.5.4",
@@ -12289,9 +12327,9 @@
             "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
             "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
         },
         },
         "enhanced-resolve": {
         "enhanced-resolve": {
-            "version": "5.9.3",
-            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
-            "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==",
+            "version": "5.10.0",
+            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
+            "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
             "requires": {
             "requires": {
                 "graceful-fs": "^4.2.4",
                 "graceful-fs": "^4.2.4",
                 "tapable": "^2.2.0"
                 "tapable": "^2.2.0"
@@ -15545,9 +15583,13 @@
             "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
             "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
         },
         },
         "vue": {
         "vue": {
-            "version": "2.6.14",
-            "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
-            "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.3.tgz",
+            "integrity": "sha512-7MTirXG7LYJi3r2jeYy7EiXIhEE85uP3kBwSxqwDsvIfy/l7+qR4U6ajw8ji1KoP+wYYg7ZY28TBTf3P3Fa4Nw==",
+            "requires": {
+                "@vue/compiler-sfc": "2.7.3",
+                "csstype": "^3.1.0"
+            }
         },
         },
         "vue-good-table": {
         "vue-good-table": {
             "version": "2.21.11",
             "version": "2.21.11",
@@ -15564,9 +15606,9 @@
             "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
             "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
         },
         },
         "vue-loader": {
         "vue-loader": {
-            "version": "15.9.8",
-            "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz",
-            "integrity": "sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==",
+            "version": "15.10.0",
+            "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz",
+            "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==",
             "requires": {
             "requires": {
                 "@vue/component-compiler-utils": "^3.1.0",
                 "@vue/component-compiler-utils": "^3.1.0",
                 "hash-sum": "^1.0.2",
                 "hash-sum": "^1.0.2",
@@ -15636,12 +15678,12 @@
             }
             }
         },
         },
         "vue-template-compiler": {
         "vue-template-compiler": {
-            "version": "2.6.14",
-            "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
-            "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==",
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.3.tgz",
+            "integrity": "sha512-QKcV4vj9akZ2zSD1+F7tci3aXpRe5DXU3TZ/ZWJPE9gInXD5UoV+Q2PrCaplj4IGIK8JXRHYaSvOXkOn7tg9Og==",
             "requires": {
             "requires": {
                 "de-indent": "^1.0.2",
                 "de-indent": "^1.0.2",
-                "he": "^1.1.0"
+                "he": "^1.2.0"
             }
             }
         },
         },
         "vue-template-es2015-compiler": {
         "vue-template-es2015-compiler": {
@@ -15802,9 +15844,9 @@
             }
             }
         },
         },
         "webpack-dev-server": {
         "webpack-dev-server": {
-            "version": "4.9.2",
-            "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.2.tgz",
-            "integrity": "sha512-H95Ns95dP24ZsEzO6G9iT+PNw4Q7ltll1GfJHV4fKphuHWgKFzGHWi4alTlTnpk1SPPk41X+l2RB7rLfIhnB9Q==",
+            "version": "4.9.3",
+            "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz",
+            "integrity": "sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==",
             "requires": {
             "requires": {
                 "@types/bonjour": "^3.5.9",
                 "@types/bonjour": "^3.5.9",
                 "@types/connect-history-api-fallback": "^1.3.5",
                 "@types/connect-history-api-fallback": "^1.3.5",
@@ -15818,7 +15860,7 @@
                 "chokidar": "^3.5.3",
                 "chokidar": "^3.5.3",
                 "colorette": "^2.0.10",
                 "colorette": "^2.0.10",
                 "compression": "^1.7.4",
                 "compression": "^1.7.4",
-                "connect-history-api-fallback": "^1.6.0",
+                "connect-history-api-fallback": "^2.0.0",
                 "default-gateway": "^6.0.3",
                 "default-gateway": "^6.0.3",
                 "express": "^4.17.3",
                 "express": "^4.17.3",
                 "graceful-fs": "^4.2.6",
                 "graceful-fs": "^4.2.6",

+ 1 - 1
package.json

@@ -13,7 +13,7 @@
         "pre-commit": "lint-staged"
         "pre-commit": "lint-staged"
     },
     },
     "dependencies": {
     "dependencies": {
-      "autoprefixer": "^10.4.1",
+        "autoprefixer": "^10.4.1",
         "axios": "^0.26.0",
         "axios": "^0.26.0",
         "cross-env": "^7.0.3",
         "cross-env": "^7.0.3",
         "dayjs": "^1.10.4",
         "dayjs": "^1.10.4",

+ 10 - 9
resources/js/components/WebauthnKeys.vue

@@ -1,17 +1,18 @@
 <template>
 <template>
   <div>
   <div>
     <div class="mt-6">
     <div class="mt-6">
-      <h3 class="font-bold text-xl">Device Authentication (U2F)</h3>
+      <h3 class="font-bold text-xl">Device Authentication (WebAuthn)</h3>
 
 
       <div class="my-4 w-24 border-b-2 border-grey-200"></div>
       <div class="my-4 w-24 border-b-2 border-grey-200"></div>
 
 
       <p class="my-6">
       <p class="my-6">
-        Webauthn Keys you have registered for 2nd factor authentication. To remove a key simply
-        click the delete button next to it. Disabling all keys will turn off 2FA on your account.
+        Hardware security keys you have registered for 2nd factor authentication. To remove a key
+        simply click the delete button next to it. Disabling all keys will turn off 2FA on your
+        account.
       </p>
       </p>
 
 
       <div>
       <div>
-        <p class="mb-0" v-if="keys.length === 0">You have not registered any Webauthn Keys.</p>
+        <p class="mb-0" v-if="keys.length === 0">You have not registered any hardware keys.</p>
 
 
         <div class="table w-full text-sm md:text-base" v-if="keys.length > 0">
         <div class="table w-full text-sm md:text-base" v-if="keys.length > 0">
           <div class="table-row">
           <div class="table-row">
@@ -19,7 +20,7 @@
             <div class="table-cell p-1 md:p-4 font-semibold">Created</div>
             <div class="table-cell p-1 md:p-4 font-semibold">Created</div>
             <div class="table-cell p-1 md:p-4 font-semibold">Enabled</div>
             <div class="table-cell p-1 md:p-4 font-semibold">Enabled</div>
             <div class="table-cell p-1 md:p-4 text-right">
             <div class="table-cell p-1 md:p-4 text-right">
-              <a href="/webauthn/keys/create" class="text-indigo-700">Add New Device</a>
+              <a href="/webauthn/keys/create" class="text-indigo-700">Add New Key</a>
             </div>
             </div>
           </div>
           </div>
           <div v-for="key in keys" :key="key.id" class="table-row even:bg-grey-50 odd:bg-white">
           <div v-for="key in keys" :key="key.id" class="table-row even:bg-grey-50 odd:bg-white">
@@ -46,15 +47,15 @@
         <h2
         <h2
           class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
           class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
         >
         >
-          Remove U2F Device
+          Remove Hardware Key
         </h2>
         </h2>
         <p v-if="keys.length === 1" class="my-4 text-grey-700">
         <p v-if="keys.length === 1" class="my-4 text-grey-700">
-          Once this device is removed, <b>Two-Factor Authentication</b> will be disabled on your
+          Once this key is removed, <b>Two-Factor Authentication</b> will be disabled on your
           account.
           account.
         </p>
         </p>
         <p v-else class="my-4 text-grey-700">
         <p v-else class="my-4 text-grey-700">
-          Once this device is removed, <b>Two-Factor Authentication</b> will still be enabled as you
-          have other U2F devices associated with your account.
+          Once this key is removed, <b>Two-Factor Authentication</b> will still be enabled as you
+          have other hardware keys associated with your account.
         </p>
         </p>
         <div class="mt-6">
         <div class="mt-6">
           <button
           <button

+ 10 - 0
resources/js/webauthn.js

@@ -236,4 +236,14 @@ WebAuthn.prototype.setNotify = function (callback) {
   this._notifyCallback = callback
   this._notifyCallback = callback
 }
 }
 
 
+!(function (e, t) {
+  'object' == typeof exports && 'object' == typeof module
+    ? (module.exports = t)
+    : 'function' == typeof define && define.amd
+    ? define([], t)
+    : 'object' == typeof exports
+    ? (exports.WebAuthn = t)
+    : (e.WebAuthn = t)
+})(this, WebAuthn)
+
 window.WebAuthn = WebAuthn
 window.WebAuthn = WebAuthn

+ 1 - 1
resources/views/layouts/app.blade.php

@@ -33,7 +33,7 @@
 
 
     <footer>
     <footer>
       <div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 lg:max-w-7xl">
       <div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 lg:max-w-7xl">
-        <div class="border-t border-grey-200 py-4 text-sm text-grey-500 text-center"><a href="https://github.com/anonaddy/anonaddy/releases/tag/v{{ PragmaRX\Version\Package\Facade::version() }}" target="_blank" rel="nofollow noreferrer noopener" class="block sm:inline">v{{ PragmaRX\Version\Package\Facade::version() }}</a></div>
+        <div class="border-t border-grey-200 py-4 text-sm text-grey-500 text-center"><a href="https://github.com/anonaddy/anonaddy/releases/tag/v{{ App\Helpers\GitVersionHelper::version() }}" target="_blank" rel="nofollow noreferrer noopener" class="block sm:inline">v{{ App\Helpers\GitVersionHelper::version() }}</a></div>
       </div>
       </div>
     </footer>
     </footer>
 
 

+ 4 - 4
resources/views/settings/show.blade.php

@@ -392,7 +392,7 @@
                 <div class="mt-4 w-24 border-b-2 border-grey-200"></div>
                 <div class="mt-4 w-24 border-b-2 border-grey-200"></div>
 
 
                 <p class="mt-6">
                 <p class="mt-6">
-                    Two-factor authentication, also known as 2FA or multi-factor, adds an extra layer of security to your account beyond your username and password. There are <b>two options for 2FA</b> - Authentication App (e.g. Google Authenticator or another, Aegis, andOTP) or U2F Device Authentication (e.g. YubiKey, SoloKey, Nitrokey).
+                    Two-factor authentication, also known as 2FA or multi-factor, adds an extra layer of security to your account beyond your username and password. There are <b>two options for 2FA</b> - Authentication App (e.g. Google Authenticator or another, Aegis, andOTP) or Hardware Security Key (e.g. YubiKey, SoloKey, Nitrokey).
                 </p>
                 </p>
 
 
                 <p class="mt-4 pb-16">
                 <p class="mt-4 pb-16">
@@ -516,19 +516,19 @@
                         <div class="pt-16">
                         <div class="pt-16">
 
 
                             <h3 class="font-bold text-xl">
                             <h3 class="font-bold text-xl">
-                                Enable Device Authentication (U2F)
+                                Enable Device Authentication (WebAuthn)
                             </h3>
                             </h3>
 
 
                             <div class="mt-4 w-24 border-b-2 border-grey-200"></div>
                             <div class="mt-4 w-24 border-b-2 border-grey-200"></div>
 
 
-                            <p class="my-6">U2F is a standard for universal two-factor authentication tokens. You can use any U2F key such as a Yubikey, Solokey, NitroKey etc.</p>
+                            <p class="my-6">WebAuthn is a new W3C global standard for secure authentication. You can use any hardware key such as a Yubikey, Solokey, NitroKey etc.</p>
 
 
                             <a
                             <a
                             type="button"
                             type="button"
                             href="{{ route('webauthn.create') }}"
                             href="{{ route('webauthn.create') }}"
                             class="block bg-cyan-400 w-full hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none text-center"
                             class="block bg-cyan-400 w-full hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none text-center"
                             >
                             >
-                                Register U2F Device
+                                Register New Hardware Key
                             </a>
                             </a>
 
 
                         </div>
                         </div>

+ 25 - 5
resources/views/vendor/webauthn/authenticate.blade.php

@@ -37,7 +37,14 @@
 
 
                     <form method="POST" onsubmit="authenticateDevice();return false" action="{{ route('webauthn.auth') }}" id="form">
                     <form method="POST" onsubmit="authenticateDevice();return false" action="{{ route('webauthn.auth') }}" id="form">
                         @csrf
                         @csrf
-                        <input type="hidden" name="data" id="data" />
+                        <input type="hidden" name="id" id="id">
+                        <input type="hidden" name="rawId" id="rawId">
+                        <input type="hidden" name="response[authenticatorData]" id="authenticatorData">
+                        <input type="hidden" name="response[clientDataJSON]" id="clientDataJSON">
+                        <input type="hidden" name="response[signature]" id="signature">
+                        <input type="hidden" name="response[userHandle]" id="userHandle">
+                        <input type="hidden" name="type" id="type">
+
                     </form>
                     </form>
 
 
                     <div class="mt-4">
                     <div class="mt-4">
@@ -102,20 +109,33 @@
         if (! /apple/i.test(navigator.vendor)) {
         if (! /apple/i.test(navigator.vendor)) {
             webauthn.sign(
             webauthn.sign(
                 publicKey,
                 publicKey,
-                function (datas) {
+                function (data) {
                     document.getElementById("success").classList.remove("hidden");
                     document.getElementById("success").classList.remove("hidden");
-                    document.getElementById("data").value = JSON.stringify(datas);
+                    document.getElementById("id").value = data.id;
+                    document.getElementById("rawId").value = data.rawId;
+                    document.getElementById("authenticatorData").value = data.response.authenticatorData;
+                    document.getElementById("clientDataJSON").value = data.response.clientDataJSON;
+                    document.getElementById("signature").value = data.response.signature;
+                    document.getElementById("userHandle").value = data.response.userHandle;
+                    document.getElementById("type").value = data.type;
                     document.getElementById("form").submit();
                     document.getElementById("form").submit();
                 }
                 }
             );
             );
         }
         }
 
 
         function authenticateDevice() {
         function authenticateDevice() {
+            document.getElementById("error").classList.add("hidden");
             webauthn.sign(
             webauthn.sign(
                 publicKey,
                 publicKey,
-                function (datas) {
+                function (data) {
                     document.getElementById("success").classList.remove("hidden");
                     document.getElementById("success").classList.remove("hidden");
-                    document.getElementById("data").value = JSON.stringify(datas);
+                    document.getElementById("id").value = data.id;
+                    document.getElementById("rawId").value = data.rawId;
+                    document.getElementById("authenticatorData").value = data.response.authenticatorData;
+                    document.getElementById("clientDataJSON").value = data.response.clientDataJSON;
+                    document.getElementById("signature").value = data.response.signature;
+                    document.getElementById("userHandle").value = data.response.userHandle;
+                    document.getElementById("type").value = data.type;
                     document.getElementById("form").submit();
                     document.getElementById("form").submit();
                 }
                 }
             );
             );

+ 12 - 4
resources/views/vendor/webauthn/register.blade.php

@@ -33,9 +33,13 @@
                         {{ trans('webauthn::messages.noButtonAdvise') }}
                         {{ trans('webauthn::messages.noButtonAdvise') }}
                     </p>
                     </p>
 
 
-                    <form method="POST" onsubmit="registerDevice();return false" class="mt-8" action="{{ route('webauthn.store') }}" id="form">
+                    <form method="POST" onsubmit="registerDevice();return false"  class="mt-8" action="{{ route('webauthn.store') }}" id="form">
                         @csrf
                         @csrf
-                        <input type="hidden" name="register" id="register">
+                        <input type="hidden" name="id" id="id">
+                        <input type="hidden" name="rawId" id="rawId">
+                        <input type="hidden" name="response[attestationObject]" id="attestationObject">
+                        <input type="hidden" name="response[clientDataJSON]" id="clientDataJSON">
+                        <input type="hidden" name="type" id="type">
 
 
                         <label for="name" class="block text-grey-700 text-sm mb-2">
                         <label for="name" class="block text-grey-700 text-sm mb-2">
                             Name:
                             Name:
@@ -119,9 +123,13 @@
 
 
             webauthn.register(
             webauthn.register(
                 publicKey,
                 publicKey,
-                function (datas) {
+                function (data) {
                     document.getElementById("success").classList.remove("hidden");
                     document.getElementById("success").classList.remove("hidden");
-                    document.getElementById("register").value = JSON.stringify(datas);
+                    document.getElementById("id").value = data.id;
+                    document.getElementById("rawId").value = data.rawId;
+                    document.getElementById("attestationObject").value = data.response.attestationObject;
+                    document.getElementById("clientDataJSON").value = data.response.clientDataJSON;
+                    document.getElementById("type").value = data.type;
                     document.getElementById("form").submit();
                     document.getElementById("form").submit();
                 }
                 }
             );
             );

+ 127 - 72
routes/api.php

@@ -1,5 +1,27 @@
 <?php
 <?php
 
 
+use App\Http\Controllers\Api\AccountDetailController;
+use App\Http\Controllers\Api\ActiveAliasController;
+use App\Http\Controllers\Api\ActiveDomainController;
+use App\Http\Controllers\Api\ActiveRuleController;
+use App\Http\Controllers\Api\ActiveUsernameController;
+use App\Http\Controllers\Api\AliasController;
+use App\Http\Controllers\Api\AliasRecipientController;
+use App\Http\Controllers\Api\AllowedRecipientController;
+use App\Http\Controllers\Api\AppVersionController;
+use App\Http\Controllers\Api\CatchAllDomainController;
+use App\Http\Controllers\Api\CatchAllUsernameController;
+use App\Http\Controllers\Api\DomainController;
+use App\Http\Controllers\Api\DomainDefaultRecipientController;
+use App\Http\Controllers\Api\DomainOptionController;
+use App\Http\Controllers\Api\EncryptedRecipientController;
+use App\Http\Controllers\Api\FailedDeliveryController;
+use App\Http\Controllers\Api\RecipientController;
+use App\Http\Controllers\Api\RecipientKeyController;
+use App\Http\Controllers\Api\ReorderRuleController;
+use App\Http\Controllers\Api\RuleController;
+use App\Http\Controllers\Api\UsernameController;
+use App\Http\Controllers\Api\UsernameDefaultRecipientController;
 use Illuminate\Support\Facades\Route;
 use Illuminate\Support\Facades\Route;
 
 
 /*
 /*
@@ -17,76 +39,109 @@ Route::group([
   'middleware' => ['auth:api', 'verified'],
   'middleware' => ['auth:api', 'verified'],
   'prefix' => 'v1'
   'prefix' => 'v1'
 ], function () {
 ], function () {
-    Route::get('/aliases', 'Api\AliasController@index');
-    Route::get('/aliases/{id}', 'Api\AliasController@show');
-    Route::post('/aliases', 'Api\AliasController@store');
-    Route::patch('/aliases/{id}', 'Api\AliasController@update');
-    Route::patch('/aliases/{id}/restore', 'Api\AliasController@restore');
-    Route::delete('/aliases/{id}', 'Api\AliasController@destroy');
-    Route::delete('/aliases/{id}/forget', 'Api\AliasController@forget');
-
-    Route::post('/active-aliases', 'Api\ActiveAliasController@store');
-    Route::delete('/active-aliases/{id}', 'Api\ActiveAliasController@destroy');
-
-    Route::post('/alias-recipients', 'Api\AliasRecipientController@store');
-
-    Route::get('/recipients', 'Api\RecipientController@index');
-    Route::get('/recipients/{id}', 'Api\RecipientController@show');
-    Route::post('/recipients', 'Api\RecipientController@store');
-    Route::delete('/recipients/{id}', 'Api\RecipientController@destroy');
-
-    Route::patch('/recipient-keys/{id}', 'Api\RecipientKeyController@update');
-    Route::delete('/recipient-keys/{id}', 'Api\RecipientKeyController@destroy');
-
-    Route::post('/encrypted-recipients', 'Api\EncryptedRecipientController@store');
-    Route::delete('/encrypted-recipients/{id}', 'Api\EncryptedRecipientController@destroy');
-
-    Route::post('/allowed-recipients', 'Api\AllowedRecipientController@store');
-    Route::delete('/allowed-recipients/{id}', 'Api\AllowedRecipientController@destroy');
-
-    Route::get('/domains', 'Api\DomainController@index');
-    Route::get('/domains/{id}', 'Api\DomainController@show');
-    Route::post('/domains', 'Api\DomainController@store');
-    Route::patch('/domains/{id}', 'Api\DomainController@update');
-    Route::delete('/domains/{id}', 'Api\DomainController@destroy');
-    Route::patch('/domains/{id}/default-recipient', 'Api\DomainDefaultRecipientController@update');
-
-    Route::post('/active-domains', 'Api\ActiveDomainController@store');
-    Route::delete('/active-domains/{id}', 'Api\ActiveDomainController@destroy');
-
-    Route::post('/catch-all-domains', 'Api\CatchAllDomainController@store');
-    Route::delete('/catch-all-domains/{id}', 'Api\CatchAllDomainController@destroy');
-
-    Route::get('/usernames', 'Api\UsernameController@index');
-    Route::get('/usernames/{id}', 'Api\UsernameController@show');
-    Route::post('/usernames', 'Api\UsernameController@store');
-    Route::patch('/usernames/{id}', 'Api\UsernameController@update');
-    Route::delete('/usernames/{id}', 'Api\UsernameController@destroy');
-    Route::patch('/usernames/{id}/default-recipient', 'Api\UsernameDefaultRecipientController@update');
-
-    Route::post('/active-usernames', 'Api\ActiveUsernameController@store');
-    Route::delete('/active-usernames/{id}', 'Api\ActiveUsernameController@destroy');
-
-    Route::post('/catch-all-usernames', 'Api\CatchAllUsernameController@store');
-    Route::delete('/catch-all-usernames/{id}', 'Api\CatchAllUsernameController@destroy');
-
-    Route::get('/rules', 'Api\RuleController@index');
-    Route::get('/rules/{id}', 'Api\RuleController@show');
-    Route::post('/rules', 'Api\RuleController@store');
-    Route::patch('/rules/{id}', 'Api\RuleController@update');
-    Route::delete('/rules/{id}', 'Api\RuleController@destroy');
-    Route::post('/reorder-rules', 'Api\ReorderRuleController@store');
-
-    Route::post('/active-rules', 'Api\ActiveRuleController@store');
-    Route::delete('/active-rules/{id}', 'Api\ActiveRuleController@destroy');
-
-    Route::get('/failed-deliveries', 'Api\FailedDeliveryController@index');
-    Route::get('/failed-deliveries/{id}', 'Api\FailedDeliveryController@show');
-    Route::delete('/failed-deliveries/{id}', 'Api\FailedDeliveryController@destroy');
-
-    Route::get('/domain-options', 'Api\DomainOptionController@index');
-
-    Route::get('/account-details', 'Api\AccountDetailController@index');
-
-    Route::get('/app-version', 'Api\AppVersionController@index');
+    Route::controller(AliasController::class)->group(function () {
+        Route::get('/aliases', 'index');
+        Route::get('/aliases/{id}', 'show');
+        Route::post('/aliases', 'store');
+        Route::patch('/aliases/{id}', 'update');
+        Route::patch('/aliases/{id}/restore', 'restore');
+        Route::delete('/aliases/{id}', 'destroy');
+        Route::delete('/aliases/{id}/forget', 'forget');
+    });
+
+    Route::controller(ActiveAliasController::class)->group(function () {
+        Route::post('/active-aliases', 'store');
+        Route::delete('/active-aliases/{id}', 'destroy');
+    });
+
+    Route::post('/alias-recipients', [AliasRecipientController::class, 'store']);
+
+    Route::controller(RecipientController::class)->group(function () {
+        Route::get('/recipients', 'index');
+        Route::get('/recipients/{id}', 'show');
+        Route::post('/recipients', 'store');
+        Route::delete('/recipients/{id}', 'destroy');
+    });
+
+    Route::controller(RecipientKeyController::class)->group(function () {
+        Route::patch('/recipient-keys/{id}', 'update');
+        Route::delete('/recipient-keys/{id}', 'destroy');
+    });
+
+    Route::controller(EncryptedRecipientController::class)->group(function () {
+        Route::post('/encrypted-recipients', 'store');
+        Route::delete('/encrypted-recipients/{id}', 'destroy');
+    });
+
+    Route::controller(AllowedRecipientController::class)->group(function () {
+        Route::post('/allowed-recipients', 'store');
+        Route::delete('/allowed-recipients/{id}', 'destroy');
+    });
+
+    Route::controller(DomainController::class)->group(function () {
+        Route::get('/domains', 'index');
+        Route::get('/domains/{id}', 'show');
+        Route::post('/domains', 'store');
+        Route::patch('/domains/{id}', 'update');
+        Route::delete('/domains/{id}', 'destroy');
+    });
+
+    Route::patch('/domains/{id}/default-recipient', [DomainDefaultRecipientController::class, 'update']);
+
+    Route::controller(ActiveDomainController::class)->group(function () {
+        Route::post('/active-domains', 'store');
+        Route::delete('/active-domains/{id}', 'destroy');
+    });
+
+    Route::controller(CatchAllDomainController::class)->group(function () {
+        Route::post('/catch-all-domains', 'store');
+        Route::delete('/catch-all-domains/{id}', 'destroy');
+    });
+
+    Route::controller(UsernameController::class)->group(function () {
+        Route::get('/usernames', 'index');
+        Route::get('/usernames/{id}', 'show');
+        Route::post('/usernames', 'store');
+        Route::patch('/usernames/{id}', 'update');
+        Route::delete('/usernames/{id}', 'destroy');
+    });
+
+    Route::patch('/usernames/{id}/default-recipient', [UsernameDefaultRecipientController::class, 'update']);
+
+    Route::controller(ActiveUsernameController::class)->group(function () {
+        Route::post('/active-usernames', 'store');
+        Route::delete('/active-usernames/{id}', 'destroy');
+    });
+
+    Route::controller(CatchAllUsernameController::class)->group(function () {
+        Route::post('/catch-all-usernames', 'store');
+        Route::delete('/catch-all-usernames/{id}', 'destroy');
+    });
+
+    Route::controller(RuleController::class)->group(function () {
+        Route::get('/rules', 'index');
+        Route::get('/rules/{id}', 'show');
+        Route::post('/rules', 'store');
+        Route::patch('/rules/{id}', 'update');
+        Route::delete('/rules/{id}', 'destroy');
+    });
+
+    Route::post('/reorder-rules', [ReorderRuleController::class, 'store']);
+
+    Route::controller(ActiveRuleController::class)->group(function () {
+        Route::post('/active-rules', 'store');
+        Route::delete('/active-rules/{id}', 'destroy');
+    });
+
+    Route::controller(FailedDeliveryController::class)->group(function () {
+        Route::get('/failed-deliveries', 'index');
+        Route::get('/failed-deliveries/{id}', 'show');
+        Route::delete('/failed-deliveries/{id}', 'destroy');
+    });
+
+    Route::get('/domain-options', [DomainOptionController::class, 'index']);
+
+    Route::get('/account-details', [AccountDetailController::class, 'index']);
+
+    Route::get('/app-version', [AppVersionController::class, 'index']);
 });
 });

+ 79 - 37
routes/web.php

@@ -1,5 +1,30 @@
 <?php
 <?php
 
 
+use App\Http\Controllers\AliasExportController;
+use App\Http\Controllers\Auth\BackupCodeController;
+use App\Http\Controllers\Auth\ForgotUsernameController;
+use App\Http\Controllers\Auth\TwoFactorAuthController;
+use App\Http\Controllers\Auth\WebauthnController;
+use App\Http\Controllers\Auth\WebauthnEnabledKeyController;
+use App\Http\Controllers\BannerLocationController;
+use App\Http\Controllers\BrowserSessionController;
+use App\Http\Controllers\DeactivateAliasController;
+use App\Http\Controllers\DefaultAliasDomainController;
+use App\Http\Controllers\DefaultAliasFormatController;
+use App\Http\Controllers\DefaultRecipientController;
+use App\Http\Controllers\DomainVerificationController;
+use App\Http\Controllers\EmailSubjectController;
+use App\Http\Controllers\FromNameController;
+use App\Http\Controllers\PasswordController;
+use App\Http\Controllers\RecipientVerificationController;
+use App\Http\Controllers\SettingController;
+use App\Http\Controllers\ShowAliasController;
+use App\Http\Controllers\ShowDomainController;
+use App\Http\Controllers\ShowFailedDeliveryController;
+use App\Http\Controllers\ShowRecipientController;
+use App\Http\Controllers\ShowRuleController;
+use App\Http\Controllers\ShowUsernameController;
+use App\Http\Controllers\UseReplyToController;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Route;
 use Illuminate\Support\Facades\Route;
 
 
@@ -15,43 +40,54 @@ use Illuminate\Support\Facades\Route;
 */
 */
 
 
 Auth::routes(['verify' => true, 'register' => config('anonaddy.enable_registration')]);
 Auth::routes(['verify' => true, 'register' => config('anonaddy.enable_registration')]);
-Route::get('/username/reminder', 'Auth\ForgotUsernameController@show')->name('username.reminder.show');
-Route::post('/username/email', 'Auth\ForgotUsernameController@sendReminderEmail')->name('username.email');
 
 
-Route::post('/login/2fa', 'Auth\TwoFactorAuthController@authenticateTwoFactor')->name('login.2fa')->middleware(['2fa', 'throttle', 'auth']);
 
 
-Route::get('/login/backup-code', 'Auth\BackupCodeController@index')->name('login.backup_code.index');
-Route::post('/login/backup-code', 'Auth\BackupCodeController@login')->name('login.backup_code.login');
+Route::controller(ForgotUsernameController::class)->group(function () {
+    Route::get('/username/reminder', 'show')->name('username.reminder.show');
+    Route::post('/username/email', 'sendReminderEmail')->name('username.email');
+});
+
+Route::post('/login/2fa', [TwoFactorAuthController::class, 'authenticateTwoFactor'])->name('login.2fa')->middleware(['2fa', 'throttle', 'auth']);
+
+Route::controller(BackupCodeController::class)->group(function () {
+    Route::get('/login/backup-code', 'index')->name('login.backup_code.index');
+    Route::post('/login/backup-code', 'login')->name('login.backup_code.login');
+});
 
 
 Route::group([
 Route::group([
     'middleware' => config('webauthn.middleware', []),
     'middleware' => config('webauthn.middleware', []),
     'domain' => config('webauthn.domain', null),
     'domain' => config('webauthn.domain', null),
     'prefix' => config('webauthn.prefix', 'webauthn'),
     'prefix' => config('webauthn.prefix', 'webauthn'),
 ], function () {
 ], function () {
-    Route::get('keys', 'Auth\WebauthnController@index')->name('webauthn.index');
-    Route::get('keys/create', 'Auth\WebauthnController@create')->name('webauthn.create');
-    Route::post('keys', 'Auth\WebauthnController@store')->name('webauthn.store');
-    Route::delete('keys/{id}', 'Auth\WebauthnController@destroy')->name('webauthn.destroy');
-    Route::post('enabled-keys', 'Auth\WebauthnEnabledKeyController@store')->name('webauthn.enabled_key.store');
-    Route::delete('enabled-keys/{id}', 'Auth\WebauthnEnabledKeyController@destroy')->name('webauthn.enabled_key.destroy');
+    Route::controller(WebauthnController::class)->group(function () {
+        Route::get('keys', 'index')->name('webauthn.index');
+        Route::get('keys/create', 'create')->name('webauthn.create');
+        Route::post('keys', 'store')->name('webauthn.store');
+        Route::delete('keys/{id}', 'destroy')->name('webauthn.destroy');
+    });
+
+    Route::controller(WebauthnEnabledKeyController::class)->group(function () {
+        Route::post('enabled-keys', 'store')->name('webauthn.enabled_key.store');
+        Route::delete('enabled-keys/{id}', 'destroy')->name('webauthn.enabled_key.destroy');
+    });
 });
 });
 
 
 Route::middleware(['auth', 'verified', '2fa', 'webauthn'])->group(function () {
 Route::middleware(['auth', 'verified', '2fa', 'webauthn'])->group(function () {
-    Route::get('/', 'ShowAliasController@index')->name('aliases.index');
+    Route::get('/', [ShowAliasController::class, 'index'])->name('aliases.index');
 
 
-    Route::get('/recipients', 'ShowRecipientController@index')->name('recipients.index');
-    Route::post('/recipients/email/resend', 'RecipientVerificationController@resend');
+    Route::get('/recipients', [ShowRecipientController::class, 'index'])->name('recipients.index');
+    Route::post('/recipients/email/resend', [RecipientVerificationController::class, 'resend']);
 
 
-    Route::get('/domains', 'ShowDomainController@index')->name('domains.index');
-    Route::get('/domains/{id}/check-sending', 'DomainVerificationController@checkSending');
+    Route::get('/domains', [ShowDomainController::class, 'index'])->name('domains.index');
+    Route::get('/domains/{id}/check-sending', [DomainVerificationController::class, 'checkSending']);
 
 
-    Route::get('/usernames', 'ShowUsernameController@index')->name('usernames.index');
+    Route::get('/usernames', [ShowUsernameController::class, 'index'])->name('usernames.index');
 
 
-    Route::get('/deactivate/{alias}', 'DeactivateAliasController@deactivate')->name('deactivate');
+    Route::get('/deactivate/{alias}', [DeactivateAliasController::class, 'deactivate'])->name('deactivate');
 
 
-    Route::get('/rules', 'ShowRuleController@index')->name('rules.index');
+    Route::get('/rules', [ShowRuleController::class, 'index'])->name('rules.index');
 
 
-    Route::get('/failed-deliveries', 'ShowFailedDeliveryController@index')->name('failed_deliveries.index');
+    Route::get('/failed-deliveries', [ShowFailedDeliveryController::class, 'index'])->name('failed_deliveries.index');
 });
 });
 
 
 
 
@@ -59,33 +95,39 @@ Route::group([
     'middleware' => ['auth', '2fa', 'webauthn'],
     'middleware' => ['auth', '2fa', 'webauthn'],
     'prefix' => 'settings'
     'prefix' => 'settings'
 ], function () {
 ], function () {
-    Route::get('/', 'SettingController@show')->name('settings.show');
-    Route::post('/account', 'SettingController@destroy')->name('account.destroy');
+    Route::controller(SettingController::class)->group(function () {
+        Route::get('/', 'show')->name('settings.show');
+        Route::post('/account', 'destroy')->name('account.destroy');
+    });
 
 
-    Route::post('/default-recipient', 'DefaultRecipientController@update')->name('settings.default_recipient');
-    Route::post('/edit-default-recipient', 'DefaultRecipientController@edit')->name('settings.edit_default_recipient');
+    Route::controller(DefaultRecipientController::class)->group(function () {
+        Route::post('/default-recipient', 'update')->name('settings.default_recipient');
+        Route::post('/edit-default-recipient', 'edit')->name('settings.edit_default_recipient');
+    });
 
 
-    Route::post('/default-alias-domain', 'DefaultAliasDomainController@update')->name('settings.default_alias_domain');
+    Route::post('/default-alias-domain', [DefaultAliasDomainController::class, 'update'])->name('settings.default_alias_domain');
 
 
-    Route::post('/default-alias-format', 'DefaultAliasFormatController@update')->name('settings.default_alias_format');
+    Route::post('/default-alias-format', [DefaultAliasFormatController::class, 'update'])->name('settings.default_alias_format');
 
 
-    Route::post('/from-name', 'FromNameController@update')->name('settings.from_name');
+    Route::post('/from-name', [FromNameController::class, 'update'])->name('settings.from_name');
 
 
-    Route::post('/email-subject', 'EmailSubjectController@update')->name('settings.email_subject');
+    Route::post('/email-subject', [EmailSubjectController::class, 'update'])->name('settings.email_subject');
 
 
-    Route::post('/banner-location', 'BannerLocationController@update')->name('settings.banner_location');
+    Route::post('/banner-location', [BannerLocationController::class, 'update'])->name('settings.banner_location');
 
 
-    Route::post('/use-reply-to', 'UseReplyToController@update')->name('settings.use_reply_to');
+    Route::post('/use-reply-to', [UseReplyToController::class, 'update'])->name('settings.use_reply_to');
 
 
-    Route::post('/password', 'PasswordController@update')->name('settings.password');
+    Route::post('/password', [PasswordController::class, 'update'])->name('settings.password');
 
 
-    Route::delete('/browser-sessions', 'BrowserSessionController@destroy')->name('browser-sessions.destroy');
+    Route::delete('/browser-sessions', [BrowserSessionController::class, 'destroy'])->name('browser-sessions.destroy');
 
 
-    Route::post('/2fa/enable', 'Auth\TwoFactorAuthController@store')->name('settings.2fa_enable');
-    Route::post('/2fa/regenerate', 'Auth\TwoFactorAuthController@update')->name('settings.2fa_regenerate');
-    Route::post('/2fa/disable', 'Auth\TwoFactorAuthController@destroy')->name('settings.2fa_disable');
+    Route::controller(TwoFactorAuthController::class)->group(function () {
+        Route::post('/2fa/enable', 'store')->name('settings.2fa_enable');
+        Route::post('/2fa/regenerate', 'update')->name('settings.2fa_regenerate');
+        Route::post('/2fa/disable', 'destroy')->name('settings.2fa_disable');
+    });
 
 
-    Route::post('/2fa/new-backup-code', 'Auth\BackupCodeController@update')->name('settings.new_backup_code');
+    Route::post('/2fa/new-backup-code', [BackupCodeController::class, 'update'])->name('settings.new_backup_code');
 
 
-    Route::get('/aliases/export', 'AliasExportController@export')->name('aliases.export');
+    Route::get('/aliases/export', [AliasExportController::class, 'export'])->name('aliases.export');
 });
 });

+ 1 - 1
tests/Feature/Api/AppVersionTest.php

@@ -2,8 +2,8 @@
 
 
 namespace Tests\Feature\Api;
 namespace Tests\Feature\Api;
 
 
+use App\Helpers\GitVersionHelper as Version;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Foundation\Testing\RefreshDatabase;
-use PragmaRX\Version\Package\Facade as Version;
 use Tests\TestCase;
 use Tests\TestCase;
 
 
 class AppVersionTest extends TestCase
 class AppVersionTest extends TestCase

+ 1 - 1
tests/Feature/Api/RulesTest.php

@@ -449,7 +449,7 @@ class RulesTest extends TestCase
 
 
     protected function getParser($file)
     protected function getParser($file)
     {
     {
-        $parser = new Parser;
+        $parser = new Parser();
 
 
         // Fix some edge cases in from name e.g. "\" John Doe \"" <johndoe@example.com>
         // Fix some edge cases in from name e.g. "\" John Doe \"" <johndoe@example.com>
         $parser->addMiddleware(function ($mimePart, $next) {
         $parser->addMiddleware(function ($mimePart, $next) {

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels