mirror of
https://github.com/anonaddy/anonaddy
synced 2026-04-25 17:15:29 +02:00
Upgrade to Laravel 9
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
#!/bin/sh
|
||||
if [ -z "$husky_skip_init" ]; then
|
||||
debug () {
|
||||
[ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1"
|
||||
if [ "$HUSKY_DEBUG" = "1" ]; then
|
||||
echo "husky (debug) - $1"
|
||||
fi
|
||||
}
|
||||
|
||||
readonly hook_name="$(basename "$0")"
|
||||
@@ -23,8 +25,7 @@ if [ -z "$husky_skip_init" ]; then
|
||||
|
||||
if [ $exitCode != 0 ]; then
|
||||
echo "husky - $hook_name hook exited with code $exitCode (error)"
|
||||
exit $exitCode
|
||||
fi
|
||||
|
||||
exit 0
|
||||
exit $exitCode
|
||||
fi
|
||||
|
||||
@@ -13,7 +13,7 @@ $finder = Symfony\Component\Finder\Finder::create()
|
||||
$config = new PhpCsFixer\Config();
|
||||
|
||||
return $config->setRules([
|
||||
'@PSR2' => true,
|
||||
'@PSR12' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'ordered_imports' => ['sort_algorithm' => 'alpha'],
|
||||
'no_unused_imports' => true,
|
||||
|
||||
@@ -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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ location = /robots.txt { access_log off; log_not_found off; }
|
||||
error_page 404 /index.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_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
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
|
||||
|
||||
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
|
||||
sudo apt install software-properties-common
|
||||
@@ -389,21 +389,21 @@ sudo add-apt-repository ppa:ondrej/php
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
Install PHP8.0 and check the version.
|
||||
Install php8.1 and check the version.
|
||||
|
||||
```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:
|
||||
|
||||
```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
|
||||
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
|
||||
```
|
||||
|
||||
Restart php8.0-fpm to reflect the changes.
|
||||
Restart php8.1-fpm to reflect the changes.
|
||||
|
||||
```bash
|
||||
sudo service php8.0-fpm restart
|
||||
sudo service php8.1-fpm restart
|
||||
```
|
||||
|
||||
## Let's Encrypt
|
||||
|
||||
@@ -55,14 +55,14 @@ class CreateUser extends Command
|
||||
'regex:/^[a-zA-Z0-9]*$/',
|
||||
'max:20',
|
||||
'unique:usernames,username',
|
||||
new NotDeletedUsername
|
||||
new NotDeletedUsername()
|
||||
],
|
||||
'email' => [
|
||||
'required',
|
||||
'email:rfc,dns',
|
||||
'max:254',
|
||||
new RegisterUniqueRecipient,
|
||||
new NotLocalRecipient
|
||||
new RegisterUniqueRecipient(),
|
||||
new NotLocalRecipient()
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
@@ -462,7 +462,7 @@ class ReceiveEmail extends Command
|
||||
|
||||
protected function getParser($file)
|
||||
{
|
||||
$parser = new Parser;
|
||||
$parser = new Parser();
|
||||
|
||||
// Fix some edge cases in from name e.g. "\" John Doe \"" <johndoe@example.com>
|
||||
$parser->addMiddleware(function ($mimePart, $next) {
|
||||
|
||||
46
app/Console/Commands/UpdateAppVersion.php
Normal file
46
app/Console/Commands/UpdateAppVersion.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -3,51 +3,45 @@
|
||||
namespace App\CustomMailDriver;
|
||||
|
||||
use Illuminate\Mail\MailManager;
|
||||
use InvalidArgumentException;
|
||||
|
||||
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 (! empty($config['encryption'])) {
|
||||
$transport->setEncryption($config['encryption']);
|
||||
if ($config === null) {
|
||||
throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
|
||||
}
|
||||
|
||||
// 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']);
|
||||
// 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']
|
||||
);
|
||||
|
||||
$transport->setPassword($config['password']);
|
||||
if ($this->app->bound('queue')) {
|
||||
$mailer->setQueue($this->app['queue']);
|
||||
}
|
||||
|
||||
return $this->configureSmtpTransport($transport, $config);
|
||||
// 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 $mailer;
|
||||
}
|
||||
}
|
||||
|
||||
167
app/CustomMailDriver/CustomMailer.php
Normal file
167
app/CustomMailDriver/CustomMailer.php
Normal file
@@ -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 {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
app/CustomMailDriver/Mime/Crypto/AlreadyEncrypted.php
Normal file
40
app/CustomMailDriver/Mime/Crypto/AlreadyEncrypted.php
Normal file
@@ -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
app/CustomMailDriver/Mime/Crypto/OpenPGPEncrypter.php
Normal file
302
app/CustomMailDriver/Mime/Crypto/OpenPGPEncrypter.php
Normal file
@@ -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
app/CustomMailDriver/Mime/Encoder/RawContentEncoder.php
Normal file
25
app/CustomMailDriver/Mime/Encoder/RawContentEncoder.php
Normal file
@@ -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
app/CustomMailDriver/Mime/Part/EncryptedPart.php
Normal file
125
app/CustomMailDriver/Mime/Part/EncryptedPart.php
Normal file
@@ -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
app/CustomMailDriver/Mime/Part/InlineImagePart.php
Normal file
48
app/CustomMailDriver/Mime/Part/InlineImagePart.php
Normal file
@@ -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
app/CustomMailDriver/Mime/Part/TextPart.php
Normal file
106
app/CustomMailDriver/Mime/Part/TextPart.php
Normal file
@@ -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
app/Exceptions/CouldNotGetVersionException.php
Normal file
13
app/Exceptions/CouldNotGetVersionException.php
Normal file
@@ -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)");
|
||||
}
|
||||
}
|
||||
@@ -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
app/Helpers/GitVersionHelper.php
Normal file
71
app/Helpers/GitVersionHelper.php
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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']));
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ class AliasExportController extends Controller
|
||||
{
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\GitVersionHelper as Version;
|
||||
use App\Http\Controllers\Controller;
|
||||
use PragmaRX\Version\Package\Facade as Version;
|
||||
|
||||
class AppVersionController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$parts = str(Version::version())->explode('.');
|
||||
|
||||
return response()->json([
|
||||
'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]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,16 +62,16 @@ class RegisterController extends Controller
|
||||
'regex:/^[a-zA-Z0-9]*$/',
|
||||
'max:20',
|
||||
'unique:usernames,username',
|
||||
new NotBlacklisted,
|
||||
new NotDeletedUsername
|
||||
new NotBlacklisted(),
|
||||
new NotDeletedUsername()
|
||||
],
|
||||
'email' => [
|
||||
'required',
|
||||
'email:rfc,dns',
|
||||
'max:254',
|
||||
'confirmed',
|
||||
new RegisterUniqueRecipient,
|
||||
new NotLocalRecipient
|
||||
new RegisterUniqueRecipient(),
|
||||
new NotLocalRecipient()
|
||||
],
|
||||
'password' => ['required', 'min:8'],
|
||||
], [
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Actions\RegisterKeyStore;
|
||||
use App\Facades\Webauthn as WebauthnFacade;
|
||||
use App\Models\WebauthnKey;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
@@ -10,10 +9,11 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
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\Services\Webauthn;
|
||||
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||
use LaravelWebauthn\Http\Requests\WebauthnRegisterRequest;
|
||||
|
||||
class WebauthnController extends ControllersWebauthnController
|
||||
{
|
||||
@@ -22,13 +22,6 @@ class WebauthnController extends ControllersWebauthnController
|
||||
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.
|
||||
*
|
||||
@@ -37,49 +30,35 @@ class WebauthnController extends ControllersWebauthnController
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$publicKey = $this->app[RegisterKeyPrepare::class]($request->user());
|
||||
$publicKey = app(PrepareCreationData::class)($request->user());
|
||||
|
||||
$request->session()->put(Webauthn::SESSION_PUBLICKEY_CREATION, $publicKey);
|
||||
|
||||
return view('vendor.webauthn.register')->with('publicKey', $publicKey);
|
||||
return app(RegisterViewResponse::class)
|
||||
->setPublicKey($request, $publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and create the Webauthn request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param WebauthnRegisterRequest $request
|
||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(Request $request)
|
||||
public function store(WebauthnRegisterRequest $request)
|
||||
{
|
||||
$request->validate([
|
||||
'register' => 'required|string',
|
||||
'name' => 'required|string|max:50'
|
||||
]);
|
||||
|
||||
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(),
|
||||
$publicKey,
|
||||
$request->input('register'),
|
||||
$request->only(['id', 'rawId', 'response', 'type']),
|
||||
$request->input('name')
|
||||
);
|
||||
|
||||
if ($webauthnKey !== null) {
|
||||
$request->session()->put(Webauthn::SESSION_WEBAUTHNID_CREATED, $webauthnKey->id);
|
||||
}
|
||||
|
||||
user()->update([
|
||||
'two_factor_enabled' => false
|
||||
]);
|
||||
|
||||
|
||||
return $this->redirectAfterSuccessRegister();
|
||||
} catch (\Exception $e) {
|
||||
return Response::json([
|
||||
|
||||
@@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
use AuthorizesRequests;
|
||||
use DispatchesJobs;
|
||||
use ValidatesRequests;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ class SettingController extends Controller
|
||||
|
||||
DeleteAccount::dispatch(user());
|
||||
|
||||
auth()->logout();
|
||||
$request->session()->invalidate();
|
||||
|
||||
return redirect()->route('login')
|
||||
->with(['status' => 'Account deleted successfully!']);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class Kernel extends HttpKernel
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Fruitcake\Cors\HandleCors::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
@@ -56,6 +56,7 @@ class Kernel extends HttpKernel
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Fideloper\Proxy\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
@@ -19,5 +19,10 @@ class TrustProxies extends Middleware
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class EditDefaultRecipientRequest extends FormRequest
|
||||
'email:rfc,dns',
|
||||
'max:254',
|
||||
'confirmed',
|
||||
new RegisterUniqueRecipient,
|
||||
new RegisterUniqueRecipient(),
|
||||
'not_in:'.$this->user()->email
|
||||
]
|
||||
];
|
||||
|
||||
@@ -28,7 +28,7 @@ class StoreAliasRecipientRequest extends FormRequest
|
||||
'recipient_ids' => [
|
||||
'array',
|
||||
'max:10',
|
||||
new VerifiedRecipientId
|
||||
new VerifiedRecipientId()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class StoreAliasRequest extends FormRequest
|
||||
'nullable',
|
||||
'array',
|
||||
'max:10',
|
||||
new VerifiedRecipientId
|
||||
new VerifiedRecipientId()
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class StoreAliasRequest extends FormRequest
|
||||
return $query->where('local_part', $this->validationData()['local_part'])
|
||||
->where('domain', $this->validationData()['domain']);
|
||||
}),
|
||||
new ValidAliasLocalPart
|
||||
new ValidAliasLocalPart()
|
||||
], function () {
|
||||
$format = $this->validationData()['format'] ?? 'random_characters';
|
||||
return $format === 'custom';
|
||||
|
||||
@@ -32,9 +32,9 @@ class StoreDomainRequest extends FormRequest
|
||||
'string',
|
||||
'max:50',
|
||||
'unique:domains',
|
||||
new ValidDomain,
|
||||
new NotLocalDomain,
|
||||
new NotUsedAsRecipientDomain
|
||||
new ValidDomain(),
|
||||
new NotLocalDomain(),
|
||||
new NotUsedAsRecipientDomain()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class StoreRecipientRequest extends FormRequest
|
||||
'string',
|
||||
'max:254',
|
||||
'email:rfc',
|
||||
new UniqueRecipient,
|
||||
new NotLocalRecipient
|
||||
new UniqueRecipient(),
|
||||
new NotLocalRecipient()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class StoreReorderRuleRequest extends FormRequest
|
||||
'ids' => [
|
||||
'required',
|
||||
'array',
|
||||
new ValidRuleId
|
||||
new ValidRuleId()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class StoreUsernameRequest extends FormRequest
|
||||
'regex:/^[a-zA-Z0-9]*$/',
|
||||
'max:20',
|
||||
'unique:usernames,username',
|
||||
new NotBlacklisted,
|
||||
new NotDeletedUsername
|
||||
new NotBlacklisted(),
|
||||
new NotDeletedUsername()
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DeleteAccount implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
protected $user;
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Helpers\AlreadyEncryptedSigner;
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use App\CustomMailDriver\Mime\Part\InlineImagePart;
|
||||
use App\Models\Alias;
|
||||
use App\Models\EmailData;
|
||||
use App\Models\Recipient;
|
||||
use App\Notifications\FailedDeliveryNotification;
|
||||
use App\Notifications\GpgKeyExpired;
|
||||
use App\Traits\CheckUserRules;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
@@ -17,13 +15,13 @@ use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
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
|
||||
{
|
||||
use Queueable, SerializesModels, CheckUserRules;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
use CheckUserRules;
|
||||
|
||||
protected $email;
|
||||
protected $user;
|
||||
@@ -41,8 +39,6 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
protected $deactivateUrl;
|
||||
protected $bannerLocation;
|
||||
protected $fingerprint;
|
||||
protected $openpgpsigner;
|
||||
protected $dkimSigner;
|
||||
protected $encryptedParts;
|
||||
protected $fromEmail;
|
||||
protected $size;
|
||||
@@ -90,23 +86,9 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->encryptedParts = $emailData->encryptedParts ?? null;
|
||||
$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;
|
||||
|
||||
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;
|
||||
|
||||
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)) {
|
||||
$replyToEmail = $this->fromEmail;
|
||||
}
|
||||
@@ -153,8 +123,8 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->email = $this
|
||||
->from($this->fromEmail, base64_decode($this->displayFrom)." '".$this->sender."'")
|
||||
->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()
|
||||
->addTextHeader('Feedback-ID', 'F:' . $this->alias->id . ':anonaddy');
|
||||
@@ -163,14 +133,14 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$message->getHeaders()
|
||||
->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()
|
||||
->addTextHeader('Message-ID', base64_decode($this->messageId));
|
||||
->addIdHeader('Message-ID', base64_decode($this->messageId));
|
||||
} 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) {
|
||||
@@ -214,33 +184,17 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
->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) {
|
||||
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();
|
||||
|
||||
$part->setContentId(base64_decode($attachment['contentId']));
|
||||
$part->setFileName(base64_decode($attachment['file_name']));
|
||||
|
||||
$message->attachPart($part);
|
||||
}
|
||||
|
||||
$message->getHeaders()
|
||||
->addTextHeader('X-Old-Cids', implode(',', $cids));
|
||||
|
||||
$message->getHeaders()
|
||||
->addTextHeader('X-New-Cids', implode(',', $newCids));
|
||||
}
|
||||
|
||||
if ($this->originalCc) {
|
||||
@@ -289,10 +243,15 @@ class ForwardEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
'location' => $this->bannerLocation,
|
||||
'deactivateUrl' => $this->deactivateUrl,
|
||||
'aliasEmail' => $this->alias->email,
|
||||
'aliasDomain' => $this->alias->domain,
|
||||
'aliasDescription' => $this->alias->description,
|
||||
'recipientId' => $this->recipientId,
|
||||
'fingerprint' => $this->fingerprint,
|
||||
'encryptedParts' => $this->encryptedParts,
|
||||
'fromEmail' => $this->sender,
|
||||
'replacedSubject' => $this->replacedSubject,
|
||||
'shouldBlock' => $this->size === 0
|
||||
'shouldBlock' => $this->size === 0,
|
||||
'needsDkimSignature' => $this->needsDkimSignature()
|
||||
]);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private function needsDkimSignature()
|
||||
{
|
||||
return $this->alias->isCustomDomain() ? $this->alias->aliasable->isVerifiedForSending() : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Helpers\AlreadyEncryptedSigner;
|
||||
use App\CustomMailDriver\Mime\Part\InlineImagePart;
|
||||
use App\Models\Alias;
|
||||
use App\Models\EmailData;
|
||||
use App\Models\User;
|
||||
@@ -13,12 +13,13 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Swift_Image;
|
||||
use Swift_Signers_DKIMSigner;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Queueable, SerializesModels, CheckUserRules;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
use CheckUserRules;
|
||||
|
||||
protected $email;
|
||||
protected $user;
|
||||
@@ -29,7 +30,6 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
protected $emailHtml;
|
||||
protected $emailAttachments;
|
||||
protected $emailInlineAttachments;
|
||||
protected $dkimSigner;
|
||||
protected $encryptedParts;
|
||||
protected $displayFrom;
|
||||
protected $fromEmail;
|
||||
@@ -69,21 +69,7 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$returnPath = $this->alias->email;
|
||||
|
||||
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');
|
||||
$returnPath = config('anonaddy.return_path');
|
||||
}
|
||||
@@ -94,14 +80,16 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->email = $this
|
||||
->from($this->fromEmail, $this->displayFrom)
|
||||
->subject(base64_decode($this->emailSubject))
|
||||
->withSwiftMessage(function ($message) use ($returnPath) {
|
||||
$message->setReturnPath($returnPath);
|
||||
->withSymfonyMessage(function (Email $message) use ($returnPath) {
|
||||
$message->returnPath($returnPath);
|
||||
|
||||
$message->getHeaders()
|
||||
->addTextHeader('Feedback-ID', 'R:' . $this->alias->id . ':anonaddy');
|
||||
|
||||
// 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) {
|
||||
$message->getHeaders()
|
||||
@@ -113,29 +101,17 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
->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) {
|
||||
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();
|
||||
|
||||
$part->setContentId(base64_decode($attachment['contentId']));
|
||||
$part->setFileName(base64_decode($attachment['file_name']));
|
||||
|
||||
$message->attachPart($part);
|
||||
}
|
||||
|
||||
$message->getHeaders()
|
||||
->addTextHeader('X-Old-Cids', implode(',', $cids));
|
||||
|
||||
$message->getHeaders()
|
||||
->addTextHeader('X-New-Cids', implode(',', $newCids));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -169,10 +145,13 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->checkRules('Replies');
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
@@ -220,4 +199,9 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
'attempted_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
private function needsDkimSignature()
|
||||
{
|
||||
return $this->alias->isCustomDomain() ? $this->alias->aliasable->isVerifiedForSending() : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Helpers\AlreadyEncryptedSigner;
|
||||
use App\CustomMailDriver\Mime\Part\InlineImagePart;
|
||||
use App\Models\Alias;
|
||||
use App\Models\EmailData;
|
||||
use App\Models\User;
|
||||
@@ -13,12 +13,13 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Swift_Image;
|
||||
use Swift_Signers_DKIMSigner;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Queueable, SerializesModels, CheckUserRules;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
use CheckUserRules;
|
||||
|
||||
protected $email;
|
||||
protected $user;
|
||||
@@ -29,7 +30,6 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
protected $emailHtml;
|
||||
protected $emailAttachments;
|
||||
protected $emailInlineAttachments;
|
||||
protected $dkimSigner;
|
||||
protected $encryptedParts;
|
||||
protected $displayFrom;
|
||||
protected $fromEmail;
|
||||
@@ -65,21 +65,7 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$returnPath = $this->alias->email;
|
||||
|
||||
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');
|
||||
$returnPath = config('anonaddy.return_path');
|
||||
}
|
||||
@@ -90,38 +76,28 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->email = $this
|
||||
->from($this->fromEmail, $this->displayFrom)
|
||||
->subject(base64_decode($this->emailSubject))
|
||||
->withSwiftMessage(function ($message) use ($returnPath) {
|
||||
$message->setReturnPath($returnPath);
|
||||
->withSymfonyMessage(function (Email $message) use ($returnPath) {
|
||||
$message->returnPath($returnPath);
|
||||
|
||||
$message->getHeaders()
|
||||
->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->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) {
|
||||
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();
|
||||
|
||||
$part->setContentId(base64_decode($attachment['contentId']));
|
||||
$part->setFileName(base64_decode($attachment['file_name']));
|
||||
|
||||
$message->attachPart($part);
|
||||
}
|
||||
|
||||
$message->getHeaders()
|
||||
->addTextHeader('X-Old-Cids', implode(',', $cids));
|
||||
|
||||
$message->getHeaders()
|
||||
->addTextHeader('X-New-Cids', implode(',', $newCids));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -155,10 +131,13 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->checkRules('Sends');
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
@@ -206,4 +185,9 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
'attempted_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
private function needsDkimSignature()
|
||||
{
|
||||
return $this->alias->isCustomDomain() ? $this->alias->aliasable->isVerifiedForSending() : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class TokenExpiringSoon extends Mailable implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
protected $user;
|
||||
protected $recipient;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
@@ -23,6 +26,7 @@ class TokenExpiringSoon extends Mailable implements ShouldQueue, ShouldBeEncrypt
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->recipient = $user->defaultRecipient;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +39,13 @@ class TokenExpiringSoon extends Mailable implements ShouldQueue, ShouldBeEncrypt
|
||||
return $this
|
||||
->subject("Your AnonAddy API token expires 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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ use Illuminate\Support\Str;
|
||||
|
||||
class Alias extends Model
|
||||
{
|
||||
use SoftDeletes, HasUuid, HasEncryptedAttributes, HasFactory;
|
||||
use SoftDeletes;
|
||||
use HasUuid;
|
||||
use HasEncryptedAttributes;
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DeletedUsername extends Model
|
||||
{
|
||||
use HasEncryptedAttributes, HasFactory;
|
||||
use HasEncryptedAttributes;
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ use Illuminate\Support\Facades\App;
|
||||
|
||||
class Domain extends Model
|
||||
{
|
||||
use HasUuid, HasEncryptedAttributes, HasFactory;
|
||||
use HasUuid;
|
||||
use HasEncryptedAttributes;
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use PhpMimeMailParser\Parser;
|
||||
|
||||
class EmailData
|
||||
@@ -28,7 +29,7 @@ class EmailData
|
||||
$this->attachments = [];
|
||||
$this->inlineAttachments = [];
|
||||
$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->inReplyTo = base64_encode($parser->getHeader('In-Reply-To'));
|
||||
$this->references = base64_encode($parser->getHeader('References'));
|
||||
|
||||
@@ -10,7 +10,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FailedDelivery extends Model
|
||||
{
|
||||
use HasUuid, HasEncryptedAttributes, HasFactory;
|
||||
use HasUuid;
|
||||
use HasEncryptedAttributes;
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@ use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class Recipient extends Model
|
||||
{
|
||||
use Notifiable, HasUuid, HasEncryptedAttributes, HasFactory;
|
||||
use Notifiable;
|
||||
use HasUuid;
|
||||
use HasEncryptedAttributes;
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
@@ -140,7 +143,7 @@ class Recipient extends Model
|
||||
*/
|
||||
public function sendEmailVerificationNotification()
|
||||
{
|
||||
$this->notify(new CustomVerifyEmail);
|
||||
$this->notify(new CustomVerifyEmail());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +153,7 @@ class Recipient extends Model
|
||||
*/
|
||||
public function sendUsernameReminderNotification()
|
||||
{
|
||||
$this->notify(new UsernameReminder);
|
||||
$this->notify(new UsernameReminder());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,8 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Rule extends Model
|
||||
{
|
||||
use HasUuid, HasFactory;
|
||||
use HasUuid;
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@ use Laravel\Passport\HasApiTokens;
|
||||
|
||||
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;
|
||||
|
||||
@@ -316,7 +320,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
*/
|
||||
public function sendEmailVerificationNotification()
|
||||
{
|
||||
$this->notify(new CustomVerifyEmail);
|
||||
$this->notify(new CustomVerifyEmail());
|
||||
}
|
||||
|
||||
public function hasVerifiedDefaultRecipient()
|
||||
@@ -465,7 +469,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
->implode('.').mt_rand(0, 999);
|
||||
}
|
||||
|
||||
public function generateRandomCharacterLocalPart(int $length) : string
|
||||
public function generateRandomCharacterLocalPart(int $length): string
|
||||
{
|
||||
$alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Username extends Model
|
||||
{
|
||||
use HasUuid, HasEncryptedAttributes, HasFactory;
|
||||
use HasUuid;
|
||||
use HasEncryptedAttributes;
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
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';
|
||||
$recipientId = $notifiable instanceof User ? $notifiable->default_recipient_id : $notifiable->id;
|
||||
|
||||
return (new MailMessage)
|
||||
return (new MailMessage())
|
||||
->subject(Lang::get('Verify Email Address'))
|
||||
->markdown('mail.verify_email', [
|
||||
'verificationUrl' => $verificationUrl,
|
||||
'recipientId' => $recipientId
|
||||
])
|
||||
->withSwiftMessage(function ($message) use ($feedbackId) {
|
||||
->withSymfonyMessage(function (Email $message) use ($feedbackId) {
|
||||
$message->getHeaders()
|
||||
->addTextHeader('Feedback-ID', $feedbackId);
|
||||
});
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Swift_SwiftException;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class DefaultRecipientUpdated extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -45,36 +44,17 @@ class DefaultRecipientUpdated extends Notification implements ShouldQueue, Shoul
|
||||
*/
|
||||
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")
|
||||
->markdown('mail.default_recipient_updated', [
|
||||
'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()
|
||||
->addTextHeader('Feedback-ID', 'DRU:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Str;
|
||||
use Swift_SwiftException;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class DisallowedReplySendAttempt extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -52,38 +51,21 @@ class DisallowedReplySendAttempt extends Notification implements ShouldQueue, Sh
|
||||
*/
|
||||
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('Disallowed reply/send from alias')
|
||||
->markdown('mail.disallowed_reply_send_attempt', [
|
||||
'aliasEmail' => $this->aliasEmail,
|
||||
'recipient' => $this->recipient,
|
||||
'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()
|
||||
->addTextHeader('Feedback-ID', 'DRSA:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Swift_SwiftException;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class DomainMxRecordsInvalid extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -45,36 +44,19 @@ class DomainMxRecordsInvalid extends Notification implements ShouldQueue, Should
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
$openpgpsigner = null;
|
||||
$recipient = $notifiable->defaultRecipient;
|
||||
$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")
|
||||
->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()
|
||||
->addTextHeader('Feedback-ID', 'DMI:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Swift_SwiftException;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class DomainUnverifiedForSending extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -47,37 +46,20 @@ class DomainUnverifiedForSending extends Notification implements ShouldQueue, Sh
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
$openpgpsigner = null;
|
||||
$recipient = $notifiable->defaultRecipient;
|
||||
$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")
|
||||
->markdown('mail.domain_unverified_for_sending', [
|
||||
'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()
|
||||
->addTextHeader('Feedback-ID', 'DUS:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Swift_SwiftException;
|
||||
|
||||
class FailedDeliveryNotification extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -49,37 +47,18 @@ class FailedDeliveryNotification extends Notification implements ShouldQueue, Sh
|
||||
*/
|
||||
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")
|
||||
->markdown('mail.failed_delivery_notification', [
|
||||
'aliasEmail' => $this->aliasEmail,
|
||||
'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()
|
||||
->addTextHeader('Feedback-ID', 'FDN:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class GpgKeyExpired extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -31,11 +32,15 @@ class GpgKeyExpired extends Notification implements ShouldQueue, ShouldBeEncrypt
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
return (new MailMessage)
|
||||
return (new MailMessage())
|
||||
->subject("Your GPG key has expired on AnonAddy")
|
||||
->markdown('mail.gpg_key_expired', [
|
||||
'recipient' => $notifiable
|
||||
]);
|
||||
])
|
||||
->withSymfonyMessage(function (Email $message) {
|
||||
$message->getHeaders()
|
||||
->addTextHeader('Feedback-ID', 'GKE:anonaddy');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Swift_SwiftException;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class NearBandwidthLimit extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -47,39 +46,22 @@ class NearBandwidthLimit extends Notification implements ShouldQueue, ShouldBeEn
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
$openpgpsigner = null;
|
||||
$recipient = $notifiable->defaultRecipient;
|
||||
$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)
|
||||
->markdown('mail.near_bandwidth_limit', [
|
||||
'bandwidthUsage' => $notifiable->bandwidth_mb,
|
||||
'bandwidthLimit' => $notifiable->getBandwidthLimitMb(),
|
||||
'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()
|
||||
->addTextHeader('Feedback-ID', 'NBL:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Str;
|
||||
use Swift_SwiftException;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -52,38 +51,19 @@ class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBe
|
||||
*/
|
||||
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')
|
||||
->markdown('mail.spam_reply_send_attempt', [
|
||||
'aliasEmail' => $this->aliasEmail,
|
||||
'recipient' => $this->recipient,
|
||||
'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()
|
||||
->addTextHeader('Feedback-ID', 'SRSA:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\OpenPGPSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Swift_SwiftException;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class UsernameReminder extends Notification implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -33,35 +32,16 @@ class UsernameReminder extends Notification implements ShouldQueue, ShouldBeEncr
|
||||
*/
|
||||
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")
|
||||
->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()
|
||||
->addTextHeader('Feedback-ID', 'UR:anonaddy');
|
||||
|
||||
if ($openpgpsigner) {
|
||||
$message->attachSigner($openpgpsigner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Swift_Preferences;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -31,8 +30,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
Blade::withoutComponentTags();
|
||||
|
||||
Swift_Preferences::getInstance()->setQPDotEscape(true);
|
||||
|
||||
Builder::macro('jsonPaginate', function (int $maxResults = null, int $defaultSize = null) {
|
||||
$maxResults = $maxResults ?? 100;
|
||||
$defaultSize = $defaultSize ?? 100;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use App\CustomMailDriver\CustomMailManager;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Mail\MailServiceProvider;
|
||||
|
||||
class CustomMailServiceProvider extends MailServiceProvider
|
||||
@@ -14,11 +15,11 @@ class CustomMailServiceProvider extends MailServiceProvider
|
||||
*/
|
||||
protected function registerIlluminateMailer()
|
||||
{
|
||||
$this->app->singleton('mail.manager', function ($app) {
|
||||
$this->app->singleton('mail.manager', static function (Application $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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,4 +33,14 @@ class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if events and listeners should be automatically discovered.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldDiscoverEvents()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class HelperServiceProvider extends ServiceProvider
|
||||
public function register()
|
||||
{
|
||||
require_once(app_path().'/Helpers/Helper.php');
|
||||
require_once(app_path().'/Helpers/GitVersionHelper.php');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,14 +22,14 @@ class RouteServiceProvider extends ServiceProvider
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public const HOME = '/';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
* Define your route model bindings, pattern filters, and other route configuration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -38,9 +38,9 @@ class RouteServiceProvider extends ServiceProvider
|
||||
$this->configureRateLimiting();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
Route::middleware('api')
|
||||
->namespace($this->namespace)
|
||||
->prefix('api')
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
Route::middleware('web')
|
||||
@@ -57,7 +57,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
protected function configureRateLimiting()
|
||||
{
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(60);
|
||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,38 +5,18 @@ namespace App\Services;
|
||||
use App\Models\WebauthnKey;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as User;
|
||||
use LaravelWebauthn\Services\Webauthn as ServicesWebauthn;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
|
||||
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.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @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
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -118,11 +118,8 @@ trait CheckUserRules
|
||||
break;
|
||||
case 'encryption':
|
||||
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;
|
||||
|
||||
@@ -7,32 +7,28 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"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/tinker": "^2.0",
|
||||
"laravel/tinker": "^2.7",
|
||||
"laravel/ui": "^3.0",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"mews/captcha": "^3.0.0",
|
||||
"php-mime-mail-parser/php-mime-mail-parser": "^7.0",
|
||||
"pragmarx/google2fa-laravel": "^2.0.0",
|
||||
"pragmarx/version": "^1.2",
|
||||
"ramsey/uuid": "^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"beyondcode/laravel-dump-server": "^1.0",
|
||||
"facade/ignition": "^2.3.6",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"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": {
|
||||
"optimize-autoloader": true,
|
||||
@@ -62,7 +58,7 @@
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi",
|
||||
"@php artisan version:absorb --ansi"
|
||||
"@php artisan anonaddy:update-app-version"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
|
||||
2529
composer.lock
generated
2529
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
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'),
|
||||
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
'asset_url' => env('ASSET_URL'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -123,6 +125,24 @@ return [
|
||||
|
||||
'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
|
||||
@@ -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(),
|
||||
|
||||
];
|
||||
|
||||
@@ -11,7 +11,7 @@ return [
|
||||
| framework when an event needs to be broadcast. You may set this to
|
||||
| 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'),
|
||||
'options' => [
|
||||
'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' => [
|
||||
|
||||
@@ -82,9 +82,9 @@ return [
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@@ -54,6 +56,9 @@ return [
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
@@ -66,7 +71,7 @@ return [
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'schema' => 'public',
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
@@ -80,6 +85,8 @@ return [
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'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'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'password' => env('REDIS_PASSWORD', null),
|
||||
|
||||
@@ -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
|
||||
| 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"
|
||||
|
|
||||
@@ -46,6 +46,7 @@ return [
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app'),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
@@ -53,6 +54,7 @@ return [
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
@@ -62,6 +64,7 @@ return [
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
|
||||
@@ -18,6 +19,22 @@ return [
|
||||
|
||||
'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
|
||||
@@ -63,11 +80,12 @@ return [
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => 'debug',
|
||||
'handler' => SyslogUdpHandler::class,
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -89,6 +107,15 @@ return [
|
||||
'driver' => 'errorlog',
|
||||
'level' => 'debug',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
140
config/mail.php
140
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.
|
||||
|
|
||||
| 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.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses",
|
||||
| "postmark", "log", "array", "failover"
|
||||
|
|
||||
*/
|
||||
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'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),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| SMTP Host Port
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'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'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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
|
||||
@@ -119,18 +115,4 @@ return [
|
||||
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'),
|
||||
|
||||
];
|
||||
|
||||
@@ -18,6 +18,7 @@ return [
|
||||
'domain' => env('MAILGUN_DOMAIN'),
|
||||
'secret' => env('MAILGUN_SECRET'),
|
||||
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
||||
'scheme' => 'https',
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
|
||||
@@ -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}'
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\WebauthnKey;
|
||||
|
||||
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',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Prefix path
|
||||
| Username / Email
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The uri prefix for all webauthn requests.
|
||||
| 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',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn Routes Prefix / Subdomain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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',
|
||||
|
||||
'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
|
||||
@@ -67,6 +122,8 @@ return [
|
||||
| - authenticate: when a user login, and has to validate Webauthn 2nd factor.
|
||||
| - 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' => [
|
||||
@@ -83,7 +140,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'sessionName' => 'webauthn_auth',
|
||||
'session_name' => 'webauthn_auth',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -226,6 +283,6 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'userless' => 'null',
|
||||
'userless' => null,
|
||||
|
||||
];
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
};
|
||||
262
package-lock.json
generated
262
package-lock.json
generated
@@ -102,12 +102,12 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"@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"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1625,9 +1625,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
@@ -1666,9 +1666,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@@ -1852,18 +1852,18 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
@@ -1969,9 +1969,9 @@
|
||||
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
|
||||
},
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
@@ -2031,6 +2031,16 @@
|
||||
"@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": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
|
||||
@@ -2858,9 +2868,9 @@
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -2872,10 +2882,10 @@
|
||||
}
|
||||
],
|
||||
"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",
|
||||
"update-browserslist-db": "^1.0.0"
|
||||
"update-browserslist-db": "^1.0.4"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -2966,9 +2976,9 @@
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -3182,9 +3192,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
@@ -3291,9 +3301,9 @@
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"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": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
@@ -3705,6 +3715,11 @@
|
||||
"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": {
|
||||
"version": "2.28.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
||||
@@ -4003,9 +4018,9 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"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": {
|
||||
"version": "6.5.4",
|
||||
@@ -4048,9 +4063,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@@ -8622,9 +8637,13 @@
|
||||
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
|
||||
},
|
||||
"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": {
|
||||
"version": "2.21.11",
|
||||
@@ -8641,9 +8660,9 @@
|
||||
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
|
||||
},
|
||||
"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": {
|
||||
"@vue/component-compiler-utils": "^3.1.0",
|
||||
"hash-sum": "^1.0.2",
|
||||
@@ -8739,12 +8758,12 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.1.0"
|
||||
"he": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-template-es2015-compiler": {
|
||||
@@ -8944,9 +8963,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"@types/bonjour": "^3.5.9",
|
||||
"@types/connect-history-api-fallback": "^1.3.5",
|
||||
@@ -8960,7 +8979,7 @@
|
||||
"chokidar": "^3.5.3",
|
||||
"colorette": "^2.0.10",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"default-gateway": "^6.0.3",
|
||||
"express": "^4.17.3",
|
||||
"graceful-fs": "^4.2.6",
|
||||
@@ -8984,6 +9003,10 @@
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^4.37.0 || ^5.0.0"
|
||||
},
|
||||
@@ -9330,12 +9353,12 @@
|
||||
}
|
||||
},
|
||||
"@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": {
|
||||
"@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"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -10367,9 +10390,9 @@
|
||||
}
|
||||
},
|
||||
"@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": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
@@ -10396,9 +10419,9 @@
|
||||
}
|
||||
},
|
||||
"@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": {
|
||||
"version": "1.1.2",
|
||||
@@ -10559,18 +10582,18 @@
|
||||
}
|
||||
},
|
||||
"@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": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"@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": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
@@ -10676,9 +10699,9 @@
|
||||
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
|
||||
},
|
||||
"@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": {
|
||||
"version": "4.0.0",
|
||||
@@ -10738,6 +10761,16 @@
|
||||
"@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": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
|
||||
@@ -11406,14 +11439,14 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"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",
|
||||
"update-browserslist-db": "^1.0.0"
|
||||
"update-browserslist-db": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
@@ -11486,9 +11519,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "4.1.2",
|
||||
@@ -11630,9 +11663,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
@@ -11725,9 +11758,9 @@
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"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": {
|
||||
"version": "2.15.3",
|
||||
@@ -12027,6 +12060,11 @@
|
||||
"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": {
|
||||
"version": "2.28.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
||||
@@ -12248,9 +12286,9 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"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": {
|
||||
"version": "6.5.4",
|
||||
@@ -12289,9 +12327,9 @@
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
|
||||
},
|
||||
"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": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@@ -15545,9 +15583,13 @@
|
||||
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
|
||||
},
|
||||
"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": {
|
||||
"version": "2.21.11",
|
||||
@@ -15564,9 +15606,9 @@
|
||||
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
|
||||
},
|
||||
"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": {
|
||||
"@vue/component-compiler-utils": "^3.1.0",
|
||||
"hash-sum": "^1.0.2",
|
||||
@@ -15636,12 +15678,12 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.1.0"
|
||||
"he": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"vue-template-es2015-compiler": {
|
||||
@@ -15802,9 +15844,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"@types/bonjour": "^3.5.9",
|
||||
"@types/connect-history-api-fallback": "^1.3.5",
|
||||
@@ -15818,7 +15860,7 @@
|
||||
"chokidar": "^3.5.3",
|
||||
"colorette": "^2.0.10",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"default-gateway": "^6.0.3",
|
||||
"express": "^4.17.3",
|
||||
"graceful-fs": "^4.2.6",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.1",
|
||||
"autoprefixer": "^10.4.1",
|
||||
"axios": "^0.26.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.10.4",
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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-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">Enabled</div>
|
||||
<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 v-for="key in keys" :key="key.id" class="table-row even:bg-grey-50 odd:bg-white">
|
||||
@@ -46,15 +47,15 @@
|
||||
<h2
|
||||
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>
|
||||
<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.
|
||||
</p>
|
||||
<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>
|
||||
<div class="mt-6">
|
||||
<button
|
||||
|
||||
10
resources/js/webauthn.js
vendored
10
resources/js/webauthn.js
vendored
@@ -236,4 +236,14 @@ WebAuthn.prototype.setNotify = function (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
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<footer>
|
||||
<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>
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -392,7 +392,7 @@
|
||||
<div class="mt-4 w-24 border-b-2 border-grey-200"></div>
|
||||
|
||||
<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 class="mt-4 pb-16">
|
||||
@@ -516,19 +516,19 @@
|
||||
<div class="pt-16">
|
||||
|
||||
<h3 class="font-bold text-xl">
|
||||
Enable Device Authentication (U2F)
|
||||
Enable Device Authentication (WebAuthn)
|
||||
</h3>
|
||||
|
||||
<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
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
Register U2F Device
|
||||
Register New Hardware Key
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,14 @@
|
||||
|
||||
<form method="POST" onsubmit="authenticateDevice();return false" action="{{ route('webauthn.auth') }}" id="form">
|
||||
@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>
|
||||
|
||||
<div class="mt-4">
|
||||
@@ -102,20 +109,33 @@
|
||||
if (! /apple/i.test(navigator.vendor)) {
|
||||
webauthn.sign(
|
||||
publicKey,
|
||||
function (datas) {
|
||||
function (data) {
|
||||
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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function authenticateDevice() {
|
||||
document.getElementById("error").classList.add("hidden");
|
||||
webauthn.sign(
|
||||
publicKey,
|
||||
function (datas) {
|
||||
function (data) {
|
||||
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();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -33,9 +33,13 @@
|
||||
{{ trans('webauthn::messages.noButtonAdvise') }}
|
||||
</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
|
||||
<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">
|
||||
Name:
|
||||
@@ -119,9 +123,13 @@
|
||||
|
||||
webauthn.register(
|
||||
publicKey,
|
||||
function (datas) {
|
||||
function (data) {
|
||||
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();
|
||||
}
|
||||
);
|
||||
|
||||
163
routes/api.php
163
routes/api.php
@@ -1,5 +1,27 @@
|
||||
<?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;
|
||||
|
||||
/*
|
||||
@@ -17,76 +39,109 @@ Route::group([
|
||||
'middleware' => ['auth:api', 'verified'],
|
||||
'prefix' => 'v1'
|
||||
], 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::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::post('/active-aliases', 'Api\ActiveAliasController@store');
|
||||
Route::delete('/active-aliases/{id}', 'Api\ActiveAliasController@destroy');
|
||||
Route::controller(ActiveAliasController::class)->group(function () {
|
||||
Route::post('/active-aliases', 'store');
|
||||
Route::delete('/active-aliases/{id}', 'destroy');
|
||||
});
|
||||
|
||||
Route::post('/alias-recipients', 'Api\AliasRecipientController@store');
|
||||
Route::post('/alias-recipients', [AliasRecipientController::class, '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::controller(RecipientController::class)->group(function () {
|
||||
Route::get('/recipients', 'index');
|
||||
Route::get('/recipients/{id}', 'show');
|
||||
Route::post('/recipients', 'store');
|
||||
Route::delete('/recipients/{id}', 'destroy');
|
||||
});
|
||||
|
||||
Route::patch('/recipient-keys/{id}', 'Api\RecipientKeyController@update');
|
||||
Route::delete('/recipient-keys/{id}', 'Api\RecipientKeyController@destroy');
|
||||
Route::controller(RecipientKeyController::class)->group(function () {
|
||||
Route::patch('/recipient-keys/{id}', 'update');
|
||||
Route::delete('/recipient-keys/{id}', 'destroy');
|
||||
});
|
||||
|
||||
Route::post('/encrypted-recipients', 'Api\EncryptedRecipientController@store');
|
||||
Route::delete('/encrypted-recipients/{id}', 'Api\EncryptedRecipientController@destroy');
|
||||
Route::controller(EncryptedRecipientController::class)->group(function () {
|
||||
Route::post('/encrypted-recipients', 'store');
|
||||
Route::delete('/encrypted-recipients/{id}', 'destroy');
|
||||
});
|
||||
|
||||
Route::post('/allowed-recipients', 'Api\AllowedRecipientController@store');
|
||||
Route::delete('/allowed-recipients/{id}', 'Api\AllowedRecipientController@destroy');
|
||||
Route::controller(AllowedRecipientController::class)->group(function () {
|
||||
Route::post('/allowed-recipients', 'store');
|
||||
Route::delete('/allowed-recipients/{id}', '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::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::post('/active-domains', 'Api\ActiveDomainController@store');
|
||||
Route::delete('/active-domains/{id}', 'Api\ActiveDomainController@destroy');
|
||||
Route::patch('/domains/{id}/default-recipient', [DomainDefaultRecipientController::class, 'update']);
|
||||
|
||||
Route::post('/catch-all-domains', 'Api\CatchAllDomainController@store');
|
||||
Route::delete('/catch-all-domains/{id}', 'Api\CatchAllDomainController@destroy');
|
||||
Route::controller(ActiveDomainController::class)->group(function () {
|
||||
Route::post('/active-domains', 'store');
|
||||
Route::delete('/active-domains/{id}', '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::controller(CatchAllDomainController::class)->group(function () {
|
||||
Route::post('/catch-all-domains', 'store');
|
||||
Route::delete('/catch-all-domains/{id}', 'destroy');
|
||||
});
|
||||
|
||||
Route::post('/active-usernames', 'Api\ActiveUsernameController@store');
|
||||
Route::delete('/active-usernames/{id}', 'Api\ActiveUsernameController@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::post('/catch-all-usernames', 'Api\CatchAllUsernameController@store');
|
||||
Route::delete('/catch-all-usernames/{id}', 'Api\CatchAllUsernameController@destroy');
|
||||
Route::patch('/usernames/{id}/default-recipient', [UsernameDefaultRecipientController::class, 'update']);
|
||||
|
||||
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::controller(ActiveUsernameController::class)->group(function () {
|
||||
Route::post('/active-usernames', 'store');
|
||||
Route::delete('/active-usernames/{id}', 'destroy');
|
||||
});
|
||||
|
||||
Route::post('/active-rules', 'Api\ActiveRuleController@store');
|
||||
Route::delete('/active-rules/{id}', 'Api\ActiveRuleController@destroy');
|
||||
Route::controller(CatchAllUsernameController::class)->group(function () {
|
||||
Route::post('/catch-all-usernames', 'store');
|
||||
Route::delete('/catch-all-usernames/{id}', '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::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::get('/domain-options', 'Api\DomainOptionController@index');
|
||||
Route::post('/reorder-rules', [ReorderRuleController::class, 'store']);
|
||||
|
||||
Route::get('/account-details', 'Api\AccountDetailController@index');
|
||||
Route::controller(ActiveRuleController::class)->group(function () {
|
||||
Route::post('/active-rules', 'store');
|
||||
Route::delete('/active-rules/{id}', 'destroy');
|
||||
});
|
||||
|
||||
Route::get('/app-version', 'Api\AppVersionController@index');
|
||||
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']);
|
||||
});
|
||||
|
||||
116
routes/web.php
116
routes/web.php
@@ -1,5 +1,30 @@
|
||||
<?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\Route;
|
||||
|
||||
@@ -15,43 +40,54 @@ use Illuminate\Support\Facades\Route;
|
||||
*/
|
||||
|
||||
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([
|
||||
'middleware' => config('webauthn.middleware', []),
|
||||
'domain' => config('webauthn.domain', null),
|
||||
'prefix' => config('webauthn.prefix', 'webauthn'),
|
||||
], 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::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'],
|
||||
'prefix' => 'settings'
|
||||
], 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');
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Tests\Feature\Api;
|
||||
|
||||
use App\Helpers\GitVersionHelper as Version;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PragmaRX\Version\Package\Facade as Version;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AppVersionTest extends TestCase
|
||||
|
||||
@@ -449,7 +449,7 @@ class RulesTest extends TestCase
|
||||
|
||||
protected function getParser($file)
|
||||
{
|
||||
$parser = new Parser;
|
||||
$parser = new Parser();
|
||||
|
||||
// Fix some edge cases in from name e.g. "\" John Doe \"" <johndoe@example.com>
|
||||
$parser->addMiddleware(function ($mimePart, $next) {
|
||||
|
||||
Reference in New Issue
Block a user