فهرست منبع

Added allow/disallow replies/sends per rcpt

Will Browning 3 سال پیش
والد
کامیت
1efe2eeac9

+ 1 - 1
README.md

@@ -426,7 +426,7 @@ They would make a Twitter announcement informing all users that they would be ke
 
 ## Is the application tested?
 
-Yes it has over 190 automated PHPUnit tests written.
+Yes it has over 200 automated PHPUnit tests written.
 
 ## How do I host this myself?
 

+ 24 - 3
app/Console/Commands/ReceiveEmail.php

@@ -12,6 +12,7 @@ use App\Models\EmailData;
 use App\Models\PostfixQueueId;
 use App\Models\Recipient;
 use App\Models\User;
+use App\Notifications\DisallowedReplySendAttempt;
 use App\Notifications\FailedDeliveryNotification;
 use App\Notifications\NearBandwidthLimit;
 use App\Notifications\SpamReplySendAttempt;
@@ -45,6 +46,7 @@ class ReceiveEmail extends Command
      */
     protected $description = 'Receive email from postfix pipe';
     protected $parser;
+    protected $senderFrom;
     protected $size;
 
     /**
@@ -70,6 +72,7 @@ class ReceiveEmail extends Command
             $file = $this->argument('file');
 
             $this->parser = $this->getParser($file);
+            $this->senderFrom = $this->getSenderFrom();
 
             $recipients = $this->getRecipients();
 
@@ -142,12 +145,17 @@ class ReceiveEmail extends Command
                 $this->checkRateLimit($user);
 
                 // Check whether this email is a reply/send from or a new email to be forwarded.
-                if (filter_var(Str::replaceLast('=', '@', $recipient['extension']), FILTER_VALIDATE_EMAIL) && $user->isVerifiedRecipient($this->getSenderFrom())) {
+                $destination = Str::replaceLast('=', '@', $recipient['extension']);
+                $validEmailDestination = filter_var($destination, FILTER_VALIDATE_EMAIL);
+                $verifiedRecipient = $user->getVerifiedRecipientByEmail($this->senderFrom);
+
+                if ($validEmailDestination && $verifiedRecipient?->can_reply_send) {
 
                     // Check if the Dmarc allow or spam headers are present from Rspamd
                     if (! $this->parser->getHeader('X-AnonAddy-Dmarc-Allow') || $this->parser->getHeader('X-AnonAddy-Spam')) {
                         // Notify user and exit
-                        $user->notify(new SpamReplySendAttempt($recipient, $this->getSenderFrom(), $this->parser->getHeader('X-AnonAddy-Authentication-Results')));
+                        $verifiedRecipient->notify(new SpamReplySendAttempt($recipient, $this->senderFrom, $this->parser->getHeader('X-AnonAddy-Authentication-Results')));
+
                         exit(0);
                     }
 
@@ -156,6 +164,11 @@ class ReceiveEmail extends Command
                     } else {
                         $this->handleSendFrom($user, $recipient, $aliasable ?? null);
                     }
+                } elseif ($validEmailDestination && $verifiedRecipient?->can_reply_send === false) {
+                    // Notify user that they have not allowed this recipient to reply and send from aliases
+                    $verifiedRecipient->notify(new DisallowedReplySendAttempt($recipient, $this->senderFrom, $this->parser->getHeader('X-AnonAddy-Authentication-Results')));
+
+                    exit(0);
                 } else {
                     $this->handleForward($user, $recipient, $aliasable ?? null);
                 }
@@ -173,7 +186,7 @@ class ReceiveEmail extends Command
     {
         $alias = Alias::find($recipient['local_part']);
 
-        if (!is_null($alias) && $alias->user->isVerifiedRecipient($this->getSenderFrom()) && ! $this->parser->getHeader('X-AnonAddy-Spam')) {
+        if ($alias && $alias->user->isVerifiedRecipient($this->senderFrom) && $this->parser->getHeader('X-AnonAddy-Dmarc-Allow')) {
             $alias->deactivate();
         }
     }
@@ -388,6 +401,14 @@ class ReceiveEmail extends Command
                     if ($failedDelivery->email_type === 'F' && $alias->emails_forwarded > 0) {
                         $alias->decrement('emails_forwarded');
                     }
+
+                    if ($failedDelivery->email_type === 'R' && $alias->emails_replied > 0) {
+                        $alias->decrement('emails_replied');
+                    }
+
+                    if ($failedDelivery->email_type === 'S' && $alias->emails_sent > 0) {
+                        $alias->decrement('emails_sent');
+                    }
                 }
             } else {
                 Log::info([

+ 2 - 0
app/Http/Controllers/Api/ActiveAdditionalUsernameController.php

@@ -10,6 +10,8 @@ class ActiveAdditionalUsernameController extends Controller
 {
     public function store(Request $request)
     {
+        $request->validate(['id' => 'required|string']);
+
         $username = user()->additionalUsernames()->findOrFail($request->id);
 
         $username->activate();

+ 2 - 0
app/Http/Controllers/Api/ActiveAliasController.php

@@ -10,6 +10,8 @@ class ActiveAliasController extends Controller
 {
     public function store(Request $request)
     {
+        $request->validate(['id' => 'required|string']);
+
         $alias = user()->aliases()->withTrashed()->findOrFail($request->id);
 
         if ($alias->trashed()) {

+ 2 - 0
app/Http/Controllers/Api/ActiveDomainController.php

@@ -10,6 +10,8 @@ class ActiveDomainController extends Controller
 {
     public function store(Request $request)
     {
+        $request->validate(['id' => 'required|string']);
+
         $domain = user()->domains()->findOrFail($request->id);
 
         $domain->activate();

+ 2 - 0
app/Http/Controllers/Api/ActiveRuleController.php

@@ -10,6 +10,8 @@ class ActiveRuleController extends Controller
 {
     public function store(Request $request)
     {
+        $request->validate(['id' => 'required|string']);
+
         $rule = user()->rules()->findOrFail($request->id);
 
         $rule->activate();

+ 30 - 0
app/Http/Controllers/Api/AllowedRecipientController.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Resources\RecipientResource;
+use Illuminate\Http\Request;
+
+class AllowedRecipientController extends Controller
+{
+    public function store(Request $request)
+    {
+        $request->validate(['id' => 'required|string']);
+
+        $recipient = user()->recipients()->findOrFail($request->id);
+
+        $recipient->update(['can_reply_send' => true]);
+
+        return new RecipientResource($recipient);
+    }
+
+    public function destroy($id)
+    {
+        $recipient = user()->recipients()->findOrFail($id);
+
+        $recipient->update(['can_reply_send' => false]);
+
+        return response('', 204);
+    }
+}

+ 2 - 0
app/Http/Controllers/Api/CatchAllAdditionalUsernameController.php

@@ -10,6 +10,8 @@ class CatchAllAdditionalUsernameController extends Controller
 {
     public function store(Request $request)
     {
+        $request->validate(['id' => 'required|string']);
+
         $username = user()->additionalUsernames()->findOrFail($request->id);
 
         $username->enableCatchAll();

+ 2 - 0
app/Http/Controllers/Api/CatchAllDomainController.php

@@ -10,6 +10,8 @@ class CatchAllDomainController extends Controller
 {
     public function store(Request $request)
     {
+        $request->validate(['id' => 'required|string']);
+
         $domain = user()->domains()->findOrFail($request->id);
 
         $domain->enableCatchAll();

+ 2 - 0
app/Http/Controllers/Api/EncryptedRecipientController.php

@@ -10,6 +10,8 @@ class EncryptedRecipientController extends Controller
 {
     public function store(Request $request)
     {
+        $request->validate(['id' => 'required|string']);
+
         $recipient = user()->recipients()->findOrFail($request->id);
 
         $recipient->update(['should_encrypt' => true]);

+ 1 - 0
app/Http/Resources/RecipientResource.php

@@ -12,6 +12,7 @@ class RecipientResource extends JsonResource
             'id' => $this->id,
             'user_id' => $this->user_id,
             'email' => $this->email,
+            'can_reply_send' => $this->can_reply_send,
             'should_encrypt' => $this->should_encrypt,
             'fingerprint' => $this->fingerprint,
             'email_verified_at' => $this->email_verified_at ? $this->email_verified_at->toDateTimeString() : null,

+ 11 - 0
app/Mail/ReplyToEmail.php

@@ -197,6 +197,17 @@ class ReplyToEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         // Send user failed delivery notification, add to failed deliveries table
         $this->user->defaultRecipient->notify(new FailedDeliveryNotification($this->alias->email, $this->sender, base64_decode($this->emailSubject)));
 
+        if ($this->size > 0) {
+            if ($this->alias->emails_replied > 0) {
+                $this->alias->decrement('emails_replied');
+            }
+
+            if ($this->user->bandwidth > $this->size) {
+                $this->user->bandwidth -= $this->size;
+                $this->user->save();
+            }
+        }
+
         $this->user->failedDeliveries()->create([
             'recipient_id' => null,
             'alias_id' => $this->alias->id,

+ 11 - 0
app/Mail/SendFromEmail.php

@@ -183,6 +183,17 @@ class SendFromEmail extends Mailable implements ShouldQueue, ShouldBeEncrypted
         // Send user failed delivery notification, add to failed deliveries table
         $this->user->defaultRecipient->notify(new FailedDeliveryNotification($this->alias->email, $this->sender, base64_decode($this->emailSubject)));
 
+        if ($this->size > 0) {
+            if ($this->alias->emails_sent > 0) {
+                $this->alias->decrement('emails_sent');
+            }
+
+            if ($this->user->bandwidth > $this->size) {
+                $this->user->bandwidth -= $this->size;
+                $this->user->save();
+            }
+        }
+
         $this->user->failedDeliveries()->create([
             'recipient_id' => null,
             'alias_id' => $this->alias->id,

+ 2 - 0
app/Models/Recipient.php

@@ -27,6 +27,7 @@ class Recipient extends Model
     protected $fillable = [
         'email',
         'user_id',
+        'can_reply_send',
         'should_encrypt',
         'fingerprint',
         'email_verified_at'
@@ -41,6 +42,7 @@ class Recipient extends Model
     protected $casts = [
         'id' => 'string',
         'user_id' => 'string',
+        'can_reply_send' => 'boolean',
         'should_encrypt' => 'boolean'
     ];
 

+ 35 - 10
app/Models/User.php

@@ -389,17 +389,42 @@ class User extends Authenticatable implements MustVerifyEmail
     public function isVerifiedRecipient($email)
     {
         return $this
-                ->verifiedRecipients()
-                ->get()
-                ->map(function ($recipient) use ($email) {
-                    if (Str::contains($email, '+')) {
-                        return strtolower($recipient->email);
-                    }
+            ->verifiedRecipients()
+            ->select(['id', 'user_id', 'email', 'email_verified_at'])
+            ->get()
+            ->map(function ($recipient) use ($email) {
+                if (Str::contains($email, '+')) {
+                    return strtolower($recipient->email);
+                }
 
-                    $withoutExtension = preg_replace('/\+[\s\S]+(?=@)/', '', $recipient->email);
-                    return strtolower($withoutExtension);
-                })
-                ->contains(strtolower($email));
+                $withoutExtension = preg_replace('/\+[\s\S]+(?=@)/', '', $recipient->email);
+                return strtolower($withoutExtension);
+            })
+            ->contains(strtolower($email));
+    }
+
+    public function getVerifiedRecipientByEmail($email)
+    {
+        return $this
+            ->verifiedRecipients()
+            ->select(['id', 'user_id', 'email', 'can_reply_send', 'email_verified_at'])
+            ->get()
+            ->first(function ($recipient) use ($email) {
+                if (Str::contains($email, '+')) {
+                    $recipientEmail = strtolower($recipient->email);
+                } else {
+                    $recipientEmail = strtolower(preg_replace('/\+[\s\S]+(?=@)/', '', $recipient->email));
+                }
+
+                // Allow either pm.me or protonmail.com domains
+                if (in_array(Str::afterLast($email, '@'), ['pm.me', 'protonmail.com'])) {
+                    $localPart = Str::beforeLast($email, '@');
+
+                    return in_array($recipientEmail, [strtolower($localPart.'@pm.me'), strtolower($localPart.'@protonmail.com')]);
+                }
+
+                return $recipientEmail === strtolower($email);
+            });
     }
 
     public function deleteKeyFromKeyring($fingerprint): void

+ 102 - 0
app/Notifications/DisallowedReplySendAttempt.php

@@ -0,0 +1,102 @@
+<?php
+
+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;
+
+class DisallowedReplySendAttempt extends Notification implements ShouldQueue, ShouldBeEncrypted
+{
+    use Queueable;
+
+    protected $aliasEmail;
+    protected $recipient;
+    protected $destination;
+    protected $authenticationResults;
+
+    /**
+     * Create a new notification instance.
+     *
+     * @return void
+     */
+    public function __construct($alias, $recipient, $authenticationResults)
+    {
+        $this->aliasEmail = $alias['local_part'] . '@' . $alias['domain'];
+        $this->recipient = $recipient;
+        $this->destination = Str::replaceLast('=', '@', $alias['extension']);
+        $this->authenticationResults = $authenticationResults;
+    }
+
+    /**
+     * Get the notification's delivery channels.
+     *
+     * @param  mixed  $notifiable
+     * @return array
+     */
+    public function via($notifiable)
+    {
+        return ['mail'];
+    }
+
+    /**
+     * Get the mail representation of the notification.
+     *
+     * @param  mixed  $notifiable
+     * @return \Illuminate\Notifications\Messages\MailMessage
+     */
+    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)
+            ->subject('Disallowed reply/send from alias')
+            ->markdown('mail.disallowed_reply_send_attempt', [
+                'aliasEmail' => $this->aliasEmail,
+                'recipient' => $this->recipient,
+                'destination' => $this->destination,
+                'authenticationResults' => $this->authenticationResults
+            ])
+            ->withSwiftMessage(function ($message) use ($openpgpsigner) {
+                $message->getHeaders()
+                        ->addTextHeader('Feedback-ID', 'DRSA:anonaddy');
+
+                if ($openpgpsigner) {
+                    $message->attachSigner($openpgpsigner);
+                }
+            });
+    }
+
+    /**
+     * Get the array representation of the notification.
+     *
+     * @param  mixed  $notifiable
+     * @return array
+     */
+    public function toArray($notifiable)
+    {
+        return [
+            //
+        ];
+    }
+}

+ 4 - 6
app/Notifications/SpamReplySendAttempt.php

@@ -53,8 +53,7 @@ class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBe
     public function toMail($notifiable)
     {
         $openpgpsigner = null;
-        $recipient = $notifiable->defaultRecipient;
-        $fingerprint = $recipient->should_encrypt ? $recipient->fingerprint : null;
+        $fingerprint = $notifiable->should_encrypt ? $notifiable->fingerprint : null;
 
         if ($fingerprint) {
             try {
@@ -64,9 +63,9 @@ class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBe
                 info($e->getMessage());
                 $openpgpsigner = null;
 
-                $recipient->update(['should_encrypt' => false]);
+                $notifiable->update(['should_encrypt' => false]);
 
-                $recipient->notify(new GpgKeyExpired);
+                $notifiable->notify(new GpgKeyExpired);
             }
         }
 
@@ -76,8 +75,7 @@ class SpamReplySendAttempt extends Notification implements ShouldQueue, ShouldBe
                 'aliasEmail' => $this->aliasEmail,
                 'recipient' => $this->recipient,
                 'destination' => $this->destination,
-                'authenticationResults' => $this->authenticationResults,
-                'recipientId' => $notifiable->default_recipient_id
+                'authenticationResults' => $this->authenticationResults
             ])
             ->withSwiftMessage(function ($message) use ($openpgpsigner) {
                 $message->getHeaders()

+ 190 - 177
composer.lock

@@ -8,16 +8,16 @@
     "packages": [
         {
             "name": "asbiin/laravel-webauthn",
-            "version": "2.0.0",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/asbiin/laravel-webauthn.git",
-                "reference": "424caae085bed85781ad7eef8904d644517d02f2"
+                "reference": "d3a7f9b9410021b84fd86b86fa77f4d2b41cc15a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/asbiin/laravel-webauthn/zipball/424caae085bed85781ad7eef8904d644517d02f2",
-                "reference": "424caae085bed85781ad7eef8904d644517d02f2",
+                "url": "https://api.github.com/repos/asbiin/laravel-webauthn/zipball/d3a7f9b9410021b84fd86b86fa77f4d2b41cc15a",
+                "reference": "d3a7f9b9410021b84fd86b86fa77f4d2b41cc15a",
                 "shasum": ""
             },
             "require": {
@@ -93,63 +93,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-02-05T11:46:47+00:00"
-        },
-        {
-            "name": "asm89/stack-cors",
-            "version": "v2.1.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/asm89/stack-cors.git",
-                "reference": "73e5b88775c64ccc0b84fb60836b30dc9d92ac4a"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/asm89/stack-cors/zipball/73e5b88775c64ccc0b84fb60836b30dc9d92ac4a",
-                "reference": "73e5b88775c64ccc0b84fb60836b30dc9d92ac4a",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2|^8.0",
-                "symfony/http-foundation": "^4|^5|^6",
-                "symfony/http-kernel": "^4|^5|^6"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^7|^9",
-                "squizlabs/php_codesniffer": "^3.5"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.1-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Asm89\\Stack\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Alexander",
-                    "email": "iam.asm89@gmail.com"
-                }
-            ],
-            "description": "Cross-origin resource sharing library and stack middleware",
-            "homepage": "https://github.com/asm89/stack-cors",
-            "keywords": [
-                "cors",
-                "stack"
-            ],
-            "support": {
-                "issues": "https://github.com/asm89/stack-cors/issues",
-                "source": "https://github.com/asm89/stack-cors/tree/v2.1.1"
-            },
-            "time": "2022-01-18T09:12:03+00:00"
+            "time": "2022-02-19T10:42:03+00:00"
         },
         {
             "name": "bacon/bacon-qr-code",
@@ -237,12 +181,12 @@
             },
             "type": "library",
             "autoload": {
-                "psr-4": {
-                    "Assert\\": "lib/Assert"
-                },
                 "files": [
                     "lib/Assert/functions.php"
-                ]
+                ],
+                "psr-4": {
+                    "Assert\\": "lib/Assert"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -1406,25 +1350,23 @@
         },
         {
             "name": "fruitcake/laravel-cors",
-            "version": "v2.0.5",
+            "version": "v2.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/fruitcake/laravel-cors.git",
-                "reference": "3a066e5cac32e2d1cdaacd6b961692778f37b5fc"
+                "reference": "361d71f00a0eea8b74da26ae75d0d207c53aa5b3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/3a066e5cac32e2d1cdaacd6b961692778f37b5fc",
-                "reference": "3a066e5cac32e2d1cdaacd6b961692778f37b5fc",
+                "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/361d71f00a0eea8b74da26ae75d0d207c53aa5b3",
+                "reference": "361d71f00a0eea8b74da26ae75d0d207c53aa5b3",
                 "shasum": ""
             },
             "require": {
-                "asm89/stack-cors": "^2.0.1",
+                "fruitcake/php-cors": "^1",
                 "illuminate/contracts": "^6|^7|^8|^9",
                 "illuminate/support": "^6|^7|^8|^9",
-                "php": ">=7.2",
-                "symfony/http-foundation": "^4|^5|^6",
-                "symfony/http-kernel": "^4.3.4|^5|^6"
+                "php": ">=7.2"
             },
             "require-dev": {
                 "laravel/framework": "^6|^7.24|^8",
@@ -1435,7 +1377,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "2.1-dev"
                 },
                 "laravel": {
                     "providers": [
@@ -1471,7 +1413,7 @@
             ],
             "support": {
                 "issues": "https://github.com/fruitcake/laravel-cors/issues",
-                "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.5"
+                "source": "https://github.com/fruitcake/laravel-cors/tree/v2.1.0"
             },
             "funding": [
                 {
@@ -1483,7 +1425,78 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-01-03T14:53:04+00:00"
+            "time": "2022-02-19T14:17:28+00:00"
+        },
+        {
+            "name": "fruitcake/php-cors",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/fruitcake/php-cors.git",
+                "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e",
+                "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4|^8.0",
+                "symfony/http-foundation": "^4.4|^5.4|^6"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^1.4",
+                "phpunit/phpunit": "^9",
+                "squizlabs/php_codesniffer": "^3.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Fruitcake\\Cors\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fruitcake",
+                    "homepage": "https://fruitcake.nl"
+                },
+                {
+                    "name": "Barryvdh",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "Cross-origin resource sharing library for the Symfony HttpFoundation",
+            "homepage": "https://github.com/fruitcake/php-cors",
+            "keywords": [
+                "cors",
+                "laravel",
+                "symfony"
+            ],
+            "support": {
+                "issues": "https://github.com/fruitcake/php-cors/issues",
+                "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0"
+            },
+            "funding": [
+                {
+                    "url": "https://fruitcake.nl",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2022-02-20T15:07:15+00:00"
         },
         {
             "name": "graham-campbell/result-type",
@@ -1591,12 +1604,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\": "src/"
-                },
                 "files": [
                     "src/functions_include.php"
-                ]
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -1956,16 +1969,16 @@
         },
         {
             "name": "laravel/framework",
-            "version": "v8.83.0",
+            "version": "v8.83.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/framework.git",
-                "reference": "29bc8779103909ebc428478b339ee6fa8703e193"
+                "reference": "bddba117f8bce2f3c9875ca1ca375a96350d0f4d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/framework/zipball/29bc8779103909ebc428478b339ee6fa8703e193",
-                "reference": "29bc8779103909ebc428478b339ee6fa8703e193",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/bddba117f8bce2f3c9875ca1ca375a96350d0f4d",
+                "reference": "bddba117f8bce2f3c9875ca1ca375a96350d0f4d",
                 "shasum": ""
             },
             "require": {
@@ -2125,7 +2138,7 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2022-02-08T15:44:51+00:00"
+            "time": "2022-02-15T15:05:20+00:00"
         },
         {
             "name": "laravel/passport",
@@ -2206,16 +2219,16 @@
         },
         {
             "name": "laravel/serializable-closure",
-            "version": "v1.1.0",
+            "version": "v1.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/serializable-closure.git",
-                "reference": "65c9faf50d567b65d81764a44526545689e3fe63"
+                "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/65c9faf50d567b65d81764a44526545689e3fe63",
-                "reference": "65c9faf50d567b65d81764a44526545689e3fe63",
+                "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/9e4b005daa20b0c161f3845040046dc9ddc1d74e",
+                "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e",
                 "shasum": ""
             },
             "require": {
@@ -2261,7 +2274,7 @@
                 "issues": "https://github.com/laravel/serializable-closure/issues",
                 "source": "https://github.com/laravel/serializable-closure"
             },
-            "time": "2022-02-01T16:29:39+00:00"
+            "time": "2022-02-11T19:23:53+00:00"
         },
         {
             "name": "laravel/tinker",
@@ -2333,16 +2346,16 @@
         },
         {
             "name": "laravel/ui",
-            "version": "v3.4.3",
+            "version": "v3.4.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/ui.git",
-                "reference": "64a0f43492c00780b2261c56cd7007a4f370d95b"
+                "reference": "1596de849ecafc0bcc891389da939012b67f9d5c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/ui/zipball/64a0f43492c00780b2261c56cd7007a4f370d95b",
-                "reference": "64a0f43492c00780b2261c56cd7007a4f370d95b",
+                "url": "https://api.github.com/repos/laravel/ui/zipball/1596de849ecafc0bcc891389da939012b67f9d5c",
+                "reference": "1596de849ecafc0bcc891389da939012b67f9d5c",
                 "shasum": ""
             },
             "require": {
@@ -2388,9 +2401,9 @@
                 "ui"
             ],
             "support": {
-                "source": "https://github.com/laravel/ui/tree/v3.4.3"
+                "source": "https://github.com/laravel/ui/tree/v3.4.4"
             },
-            "time": "2022-02-08T14:19:32+00:00"
+            "time": "2022-02-10T22:38:33+00:00"
         },
         {
             "name": "lcobucci/clock",
@@ -2528,16 +2541,16 @@
         },
         {
             "name": "league/commonmark",
-            "version": "2.2.1",
+            "version": "2.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/commonmark.git",
-                "reference": "f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a"
+                "reference": "13d7751377732637814f0cda0e3f6d3243f9f769"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a",
-                "reference": "f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/13d7751377732637814f0cda0e3f6d3243f9f769",
+                "reference": "13d7751377732637814f0cda0e3f6d3243f9f769",
                 "shasum": ""
             },
             "require": {
@@ -2628,7 +2641,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-25T14:37:33+00:00"
+            "time": "2022-02-13T15:00:57+00:00"
         },
         {
             "name": "league/config",
@@ -3428,31 +3441,31 @@
         },
         {
             "name": "mews/captcha",
-            "version": "3.2.6",
+            "version": "3.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/mewebstudio/captcha.git",
-                "reference": "42c1b320e4cad1f6ec9a395da36d7eda5b67d122"
+                "reference": "c4dec4963ea19a89aaf679fac1921323dd7decd8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/mewebstudio/captcha/zipball/42c1b320e4cad1f6ec9a395da36d7eda5b67d122",
-                "reference": "42c1b320e4cad1f6ec9a395da36d7eda5b67d122",
+                "url": "https://api.github.com/repos/mewebstudio/captcha/zipball/c4dec4963ea19a89aaf679fac1921323dd7decd8",
+                "reference": "c4dec4963ea19a89aaf679fac1921323dd7decd8",
                 "shasum": ""
             },
             "require": {
                 "ext-gd": "*",
-                "illuminate/config": "~5|^6|^7|^8",
-                "illuminate/filesystem": "~5|^6|^7|^8",
-                "illuminate/hashing": "~5|^6|^7|^8",
-                "illuminate/session": "~5|^6|^7|^8",
-                "illuminate/support": "~5|^6|^7|^8",
+                "illuminate/config": "~5|^6|^7|^8|^9",
+                "illuminate/filesystem": "~5|^6|^7|^8|^9",
+                "illuminate/hashing": "~5|^6|^7|^8|^9",
+                "illuminate/session": "~5|^6|^7|^8|^9",
+                "illuminate/support": "~5|^6|^7|^8|^9",
                 "intervention/image": "~2.5",
                 "php": "^7.2|^8.0"
             },
             "require-dev": {
                 "mockery/mockery": "^1.0",
-                "phpunit/phpunit": "^8.5"
+                "phpunit/phpunit": "^8.5|^9.0"
             },
             "type": "package",
             "extra": {
@@ -3466,12 +3479,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Mews\\Captcha\\": "src/"
-                },
                 "files": [
                     "src/helpers.php"
-                ]
+                ],
+                "psr-4": {
+                    "Mews\\Captcha\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -3495,9 +3508,9 @@
             ],
             "support": {
                 "issues": "https://github.com/mewebstudio/captcha/issues",
-                "source": "https://github.com/mewebstudio/captcha/tree/3.2.6"
+                "source": "https://github.com/mewebstudio/captcha/tree/3.2.7"
             },
-            "time": "2021-04-22T18:42:48+00:00"
+            "time": "2022-02-12T15:58:32+00:00"
         },
         {
             "name": "monolog/monolog",
@@ -3660,16 +3673,16 @@
         },
         {
             "name": "nesbot/carbon",
-            "version": "2.56.0",
+            "version": "2.57.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
-                "reference": "626ec8cbb724cd3c3400c3ed8f730545b744e3f4"
+                "reference": "4a54375c21eea4811dbd1149fe6b246517554e78"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/626ec8cbb724cd3c3400c3ed8f730545b744e3f4",
-                "reference": "626ec8cbb724cd3c3400c3ed8f730545b744e3f4",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4a54375c21eea4811dbd1149fe6b246517554e78",
+                "reference": "4a54375c21eea4811dbd1149fe6b246517554e78",
                 "shasum": ""
             },
             "require": {
@@ -3752,7 +3765,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-21T17:08:38+00:00"
+            "time": "2022-02-13T18:13:33+00:00"
         },
         {
             "name": "nette/schema",
@@ -4363,16 +4376,16 @@
         },
         {
             "name": "phpoffice/phpspreadsheet",
-            "version": "1.21.0",
+            "version": "1.22.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
-                "reference": "1a359d2ccbb89c05f5dffb32711a95f4afc67964"
+                "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/1a359d2ccbb89c05f5dffb32711a95f4afc67964",
-                "reference": "1a359d2ccbb89c05f5dffb32711a95f4afc67964",
+                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a9e29b4f386a08a151a33578e80ef1747037a48",
+                "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48",
                 "shasum": ""
             },
             "require": {
@@ -4403,7 +4416,7 @@
                 "dompdf/dompdf": "^1.0",
                 "friendsofphp/php-cs-fixer": "^3.2",
                 "jpgraph/jpgraph": "^4.0",
-                "mpdf/mpdf": "^8.0",
+                "mpdf/mpdf": "8.0.17",
                 "phpcompatibility/php-compatibility": "^9.3",
                 "phpstan/phpstan": "^1.1",
                 "phpstan/phpstan-phpunit": "^1.0",
@@ -4461,9 +4474,9 @@
             ],
             "support": {
                 "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
-                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.21.0"
+                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.22.0"
             },
-            "time": "2022-01-06T11:10:08+00:00"
+            "time": "2022-02-18T12:57:07+00:00"
         },
         {
             "name": "phpoption/phpoption",
@@ -4947,8 +4960,8 @@
             },
             "autoload": {
                 "psr-4": {
-                    "PragmaRX\\Yaml\\Package\\": "src/package",
-                    "PragmaRX\\Yaml\\Tests\\": "tests/"
+                    "PragmaRX\\Yaml\\Tests\\": "tests/",
+                    "PragmaRX\\Yaml\\Package\\": "src/package"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -7145,12 +7158,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
-                },
                 "files": [
                     "bootstrap.php"
-                ]
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -9029,24 +9042,24 @@
     "packages-dev": [
         {
             "name": "beyondcode/laravel-dump-server",
-            "version": "1.7.0",
+            "version": "1.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/beyondcode/laravel-dump-server.git",
-                "reference": "e27c7b942ab62f6ac7168359393d328ec5215b89"
+                "reference": "33a19c632655c1d4f16c0bc67ce6ba0504116b8a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/beyondcode/laravel-dump-server/zipball/e27c7b942ab62f6ac7168359393d328ec5215b89",
-                "reference": "e27c7b942ab62f6ac7168359393d328ec5215b89",
+                "url": "https://api.github.com/repos/beyondcode/laravel-dump-server/zipball/33a19c632655c1d4f16c0bc67ce6ba0504116b8a",
+                "reference": "33a19c632655c1d4f16c0bc67ce6ba0504116b8a",
                 "shasum": ""
             },
             "require": {
-                "illuminate/console": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0",
-                "illuminate/http": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0",
-                "illuminate/support": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0",
+                "illuminate/console": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0|^9.0",
+                "illuminate/http": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0|^9.0",
+                "illuminate/support": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0|^9.0",
                 "php": ">=7.2.5",
-                "symfony/var-dumper": "^5.0"
+                "symfony/var-dumper": "^5.0|^6.0"
             },
             "require-dev": {
                 "larapack/dd": "^1.0",
@@ -9061,12 +9074,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "BeyondCode\\DumpServer\\": "src"
-                },
                 "files": [
                     "helpers.php"
-                ]
+                ],
+                "psr-4": {
+                    "BeyondCode\\DumpServer\\": "src"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -9088,9 +9101,9 @@
             ],
             "support": {
                 "issues": "https://github.com/beyondcode/laravel-dump-server/issues",
-                "source": "https://github.com/beyondcode/laravel-dump-server/tree/1.7.0"
+                "source": "https://github.com/beyondcode/laravel-dump-server/tree/1.8.0"
             },
-            "time": "2020-12-15T10:57:43+00:00"
+            "time": "2022-02-12T12:37:34+00:00"
         },
         {
             "name": "composer/pcre",
@@ -9485,12 +9498,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Facade\\FlareClient\\": "src"
-                },
                 "files": [
                     "src/helpers.php"
-                ]
+                ],
+                "psr-4": {
+                    "Facade\\FlareClient\\": "src"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -10201,16 +10214,16 @@
         },
         {
             "name": "phar-io/version",
-            "version": "3.1.1",
+            "version": "3.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phar-io/version.git",
-                "reference": "15a90844ad40f127afd244c0cad228de2a80052a"
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/version/zipball/15a90844ad40f127afd244c0cad228de2a80052a",
-                "reference": "15a90844ad40f127afd244c0cad228de2a80052a",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
                 "shasum": ""
             },
             "require": {
@@ -10246,9 +10259,9 @@
             "description": "Library for handling version information and constraints",
             "support": {
                 "issues": "https://github.com/phar-io/version/issues",
-                "source": "https://github.com/phar-io/version/tree/3.1.1"
+                "source": "https://github.com/phar-io/version/tree/3.2.1"
             },
-            "time": "2022-02-07T21:56:48+00:00"
+            "time": "2022-02-21T01:04:05+00:00"
         },
         {
             "name": "php-cs-fixer/diff",
@@ -10531,16 +10544,16 @@
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "9.2.10",
+            "version": "9.2.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687"
+                "reference": "665a1ac0a763c51afc30d6d130dac0813092b17f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687",
-                "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/665a1ac0a763c51afc30d6d130dac0813092b17f",
+                "reference": "665a1ac0a763c51afc30d6d130dac0813092b17f",
                 "shasum": ""
             },
             "require": {
@@ -10596,7 +10609,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10"
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.11"
             },
             "funding": [
                 {
@@ -10604,7 +10617,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2021-12-05T09:12:13+00:00"
+            "time": "2022-02-18T12:46:09+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
@@ -10849,16 +10862,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "9.5.13",
+            "version": "9.5.14",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "597cb647654ede35e43b137926dfdfef0fb11743"
+                "reference": "1883687169c017d6ae37c58883ca3994cfc34189"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/597cb647654ede35e43b137926dfdfef0fb11743",
-                "reference": "597cb647654ede35e43b137926dfdfef0fb11743",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1883687169c017d6ae37c58883ca3994cfc34189",
+                "reference": "1883687169c017d6ae37c58883ca3994cfc34189",
                 "shasum": ""
             },
             "require": {
@@ -10936,7 +10949,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.13"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.14"
             },
             "funding": [
                 {
@@ -10948,7 +10961,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-01-24T07:33:35+00:00"
+            "time": "2022-02-18T12:54:07+00:00"
         },
         {
             "name": "sebastian/cli-parser",
@@ -11456,16 +11469,16 @@
         },
         {
             "name": "sebastian/global-state",
-            "version": "5.0.4",
+            "version": "5.0.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "19c519631c5a511b7ed0ad64a6713fdb3fd25fe4"
+                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/19c519631c5a511b7ed0ad64a6713fdb3fd25fe4",
-                "reference": "19c519631c5a511b7ed0ad64a6713fdb3fd25fe4",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
                 "shasum": ""
             },
             "require": {
@@ -11508,7 +11521,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.4"
+                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
             },
             "funding": [
                 {
@@ -11516,7 +11529,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-02-10T07:01:19+00:00"
+            "time": "2022-02-14T08:28:10+00:00"
         },
         {
             "name": "sebastian/lines-of-code",

+ 4 - 4
config/version.yml

@@ -3,11 +3,11 @@ blade-directive: version
 current:
   label: v
   major: 0
-  minor: 9
-  patch: 1
-  prerelease: 1-g5ee6abf
+  minor: 10
+  patch: 0
+  prerelease: ''
   buildmetadata: ''
-  commit: 5ee6ab
+  commit: 1e93c6
   timestamp:
     year: 2020
     month: 10

+ 32 - 0
database/migrations/2022_02_21_123001_add_can_reply_send_to_recipients_table.php

@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddCanReplySendToRecipientsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('recipients', function (Blueprint $table) {
+            $table->boolean('can_reply_send')->after('email')->default(true);
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('recipients', function (Blueprint $table) {
+            $table->dropColumn('can_reply_send');
+        });
+    }
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 243 - 251
package-lock.json


+ 44 - 0
resources/js/pages/Recipients.vue

@@ -110,6 +110,16 @@
           </span>
           <span v-else class="block text-grey-500 text-sm">{{ props.row.aliases.length }}</span>
         </span>
+        <span
+          v-else-if="props.column.field === 'can_reply_send'"
+          class="flex justify-center items-center"
+        >
+          <Toggle
+            v-model="rows[props.row.originalIndex].can_reply_send"
+            @on="allowRepliesSends(props.row.id)"
+            @off="disallowRepliesSends(props.row.id)"
+          />
+        </span>
         <span v-else-if="props.column.field === 'should_encrypt'">
           <span v-if="props.row.fingerprint" class="flex">
             <Toggle
@@ -399,6 +409,12 @@ export default {
           sortFn: this.sortRecipientAliases,
           globalSearchDisabled: true,
         },
+        {
+          label: 'Can Reply/Send',
+          field: 'can_reply_send',
+          type: 'boolean',
+          globalSearchDisabled: true,
+        },
         {
           label: 'Encryption',
           field: 'should_encrypt',
@@ -659,6 +675,34 @@ export default {
           this.error()
         })
     },
+    allowRepliesSends(id) {
+      axios
+        .post(
+          `/api/v1/allowed-recipients`,
+          JSON.stringify({
+            id: id,
+          }),
+          {
+            headers: { 'Content-Type': 'application/json' },
+          }
+        )
+        .then(response => {
+          //
+        })
+        .catch(error => {
+          this.error()
+        })
+    },
+    disallowRepliesSends(id) {
+      axios
+        .delete(`/api/v1/allowed-recipients/${id}`)
+        .then(response => {
+          //
+        })
+        .catch(error => {
+          this.error()
+        })
+    },
     openRecipientKeyModal(recipient) {
       this.addRecipientKeyModalOpen = true
       this.recipientToAddKey = recipient

+ 19 - 0
resources/views/mail/disallowed_reply_send_attempt.blade.php

@@ -0,0 +1,19 @@
+@component('mail::message')
+
+# Disallowed Reply/Send Attempt
+
+An attempt to send or reply from your alias **{{ $aliasEmail }}** was just made which failed because your recipient **{{ $recipient }}** has disallowed replying and sending.
+
+The attempt was trying to send the message to the following destination: **{{ $destination }}**
+
+If this attempt was made by you, then you need to visit the [recipients page]({{ config('app.url').'/recipients' }}) and update the "can reply/send" setting for **{{ $recipient }}**.
+
+If this attempt was not made by you, then someone else may be attempting to send a message from your alias. Make sure you have a suitable DMARC policy in place (with p=quarantine or p=reject) along with SPF and DKIM records to protect your recipient's email address from being spoofed.
+
+@if($authenticationResults)
+These are the authentication results for the message:
+
+{{ $authenticationResults }}
+@endif
+
+@endcomponent

+ 2 - 2
resources/views/mail/spam_reply_send_attempt.blade.php

@@ -14,8 +14,8 @@ These are the authentication results for the message:
 {{ $authenticationResults }}
 @endif
 
-If this attempt was made by yourself then you need to @if($authenticationResults) inspect the authentication results above and @endif make sure your recipient's domain (**{{ \Illuminate\Support\Str::afterLast($recipient, '@') }}**) has the correct DNS records in place; SPF, DKIM and DMARC.
+If this attempt was made by yourself, then you need to @if($authenticationResults) inspect the authentication results above and @endif make sure your recipient's domain (**{{ \Illuminate\Support\Str::afterLast($recipient, '@') }}**) has the correct DNS records in place; SPF, DKIM and DMARC.
 
-If this attempt was not made by you then someone else may be attempting to send a message from your alias. Make sure you have a suitable DMARC policy in place (with p=quarantine or p=reject) along with SPF and DKIM records to protect your recipient's email address from being spoofed.
+If this attempt was not made by you, then someone else may be attempting to send a message from your alias. Make sure you have a suitable DMARC policy in place (with p=quarantine or p=reject) along with SPF and DKIM records to protect your recipient's email address from being spoofed.
 
 @endcomponent

+ 3 - 0
routes/api.php

@@ -41,6 +41,9 @@ Route::group([
     Route::post('/encrypted-recipients', 'Api\EncryptedRecipientController@store');
     Route::delete('/encrypted-recipients/{id}', 'Api\EncryptedRecipientController@destroy');
 
+    Route::post('/allowed-recipients', 'Api\AllowedRecipientController@store');
+    Route::delete('/allowed-recipients/{id}', 'Api\AllowedRecipientController@destroy');
+
     Route::get('/domains', 'Api\DomainController@index');
     Route::get('/domains/{id}', 'Api\DomainController@show');
     Route::post('/domains', 'Api\DomainController@store');

+ 30 - 0
tests/Feature/Api/RecipientsTest.php

@@ -277,4 +277,34 @@ class RecipientsTest extends TestCase
         $response->assertStatus(204);
         $this->assertFalse($this->user->recipients[0]->should_encrypt);
     }
+
+    /** @test */
+    public function user_can_allow_recipient_to_send_or_reply()
+    {
+        $recipient = Recipient::factory()->create([
+            'user_id' => $this->user->id,
+            'can_reply_send' => false
+        ]);
+
+        $response = $this->json('POST', '/api/v1/allowed-recipients/', [
+            'id' => $recipient->id
+        ]);
+
+        $response->assertStatus(200);
+        $this->assertEquals(true, $response->getData()->data->can_reply_send);
+    }
+
+    /** @test */
+    public function user_can_disallow_recipient_from_sending_or_replying()
+    {
+        $recipient = Recipient::factory()->create([
+            'user_id' => $this->user->id,
+            'can_reply_send' => true
+        ]);
+
+        $response = $this->json('DELETE', '/api/v1/allowed-recipients/'.$recipient->id);
+
+        $response->assertStatus(204);
+        $this->assertFalse($this->user->recipients[0]->can_reply_send);
+    }
 }

+ 1 - 0
tests/emails/email_unsubscribe.eml

@@ -2,6 +2,7 @@ Date: Wed, 20 Feb 2019 15:00:00 +0100 (CET)
 From: Will <will@anonaddy.com>
 To: <8f36380f-df4e-4875-bb12-9c4448573712@unsubscribe.anonaddy.com>
 Subject: Unsubscribe
+X-AnonAddy-Dmarc-Allow: Yes
 Content-Type: multipart/mixed; boundary="----=_Part_10031_1199410393.1550677940425"
 
 ------=_Part_10031_1199410393.1550677940425

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است