Explorar o código

Move account username to usernames table

Will Browning %!s(int64=3) %!d(string=hai) anos
pai
achega
cd15c39722
Modificáronse 59 ficheiros con 1038 adicións e 887 borrados
  1. 13 27
      SELF-HOSTING.md
  2. 13 5
      app/Console/Commands/CreateUser.php
  3. 19 14
      app/Console/Commands/ListUsers.php
  4. 11 16
      app/Console/Commands/ReceiveEmail.php
  5. 5 5
      app/Http/Controllers/Api/ActiveUsernameController.php
  6. 0 54
      app/Http/Controllers/Api/AdditionalUsernameController.php
  7. 0 25
      app/Http/Controllers/Api/AdditionalUsernameDefaultRecipientController.php
  8. 4 4
      app/Http/Controllers/Api/AliasController.php
  9. 5 5
      app/Http/Controllers/Api/CatchAllUsernameController.php
  10. 1 1
      app/Http/Controllers/Api/RecipientController.php
  11. 58 0
      app/Http/Controllers/Api/UsernameController.php
  12. 25 0
      app/Http/Controllers/Api/UsernameDefaultRecipientController.php
  13. 5 1
      app/Http/Controllers/Auth/ForgotPasswordController.php
  14. 29 1
      app/Http/Controllers/Auth/LoginController.php
  15. 8 3
      app/Http/Controllers/Auth/RegisterController.php
  16. 10 3
      app/Http/Controllers/Auth/ResetPasswordController.php
  17. 0 19
      app/Http/Controllers/CatchAllController.php
  18. 3 1
      app/Http/Controllers/DefaultRecipientController.php
  19. 4 4
      app/Http/Controllers/ShowRecipientController.php
  20. 2 2
      app/Http/Controllers/ShowUsernameController.php
  21. 2 3
      app/Http/Requests/StoreUsernameRequest.php
  22. 0 30
      app/Http/Requests/UpdateCatchAllRequest.php
  23. 1 1
      app/Http/Requests/UpdateUsernameDefaultRecipientRequest.php
  24. 1 1
      app/Http/Requests/UpdateUsernameRequest.php
  25. 1 1
      app/Http/Resources/UsernameResource.php
  26. 3 6
      app/Jobs/DeleteAccount.php
  27. 1 1
      app/Models/Alias.php
  28. 2 2
      app/Models/Recipient.php
  29. 34 17
      app/Models/User.php
  30. 7 6
      app/Models/Username.php
  31. 117 114
      composer.lock
  32. 3 3
      config/version.yml
  33. 2 2
      database/factories/UserFactory.php
  34. 3 3
      database/factories/UsernameFactory.php
  35. 131 0
      database/migrations/2022_02_25_091005_move_account_username_to_usernames_table.php
  36. 298 298
      package-lock.json
  37. 1 1
      package.json
  38. 1 1
      resources/js/pages/Aliases.vue
  39. 25 12
      resources/js/pages/Usernames.vue
  40. 0 45
      resources/views/settings/show.blade.php
  41. 1 1
      resources/views/usernames/index.blade.php
  42. 12 12
      routes/api.php
  43. 1 3
      routes/web.php
  44. 7 2
      tests/Feature/Api/AliasesTest.php
  45. 4 0
      tests/Feature/Api/ApiTokensTest.php
  46. 3 0
      tests/Feature/Api/FailedDeliveriesTest.php
  47. 3 0
      tests/Feature/Api/RulesTest.php
  48. 60 46
      tests/Feature/Api/UsernamesTest.php
  49. 22 1
      tests/Feature/LoginTest.php
  50. 17 14
      tests/Feature/ReceiveEmailTest.php
  51. 14 20
      tests/Feature/RegistrationTest.php
  52. 5 1
      tests/Feature/ReplyToEmailTest.php
  53. 5 1
      tests/Feature/SendFromEmailTest.php
  54. 20 42
      tests/Feature/SettingsTest.php
  55. 4 1
      tests/Feature/ShowFailedDeliveriesTest.php
  56. 6 6
      tests/Feature/ShowUsernamesTest.php
  57. 3 0
      tests/Unit/AliasTest.php
  58. 3 0
      tests/Unit/UserTest.php
  59. 0 0
      tests/emails/email_username.eml

+ 13 - 27
SELF-HOSTING.md

@@ -526,13 +526,13 @@ user = anonaddy
 password = your-database-password
 hosts = 127.0.0.1
 dbname = anonaddy_database
-query = SELECT (SELECT 1 FROM users WHERE '%s' IN (CONCAT(username, '.example.com'))) AS users, (SELECT 1 FROM additional_usernames WHERE '%s' IN (CONCAT(additional_usernames.username, '.example.com'))) AS usernames, (SELECT 1 FROM domains WHERE domains.domain = '%s' AND domains.domain_verified_at IS NOT NULL) AS domains LIMIT 1;
+query = SELECT (SELECT 1 FROM usernames WHERE '%s' IN (CONCAT(username, '.example.com'))) AS usernames, (SELECT 1 FROM domains WHERE domains.domain = '%s' AND domains.domain_verified_at IS NOT NULL) AS domains LIMIT 1;
 ```
 
 If you need to add multiple domains then just update the above query to:
 
 ```sql
-query = SELECT (SELECT 1 FROM users WHERE '%s' IN (CONCAT(username, '.example.com'),CONCAT(username, '.example2.com'))) AS users, (SELECT 1 FROM additional_usernames WHERE '%s' IN (CONCAT(additional_usernames.username, '.example.com'),CONCAT(additional_usernames.username, '.example2.com'))) AS usernames, (SELECT 1 FROM domains WHERE domains.domain = '%s' AND domains.domain_verified_at IS NOT NULL) AS domains LIMIT 1;
+query = SELECT (SELECT 1 FROM usernames WHERE '%s' IN (CONCAT(username, '.example.com'),CONCAT(username, '.example2.com'))) AS usernames, (SELECT 1 FROM domains WHERE domains.domain = '%s' AND domains.domain_verified_at IS NOT NULL) AS domains LIMIT 1;
 ```
 
 This file is responsible for determining whether the server should accept email for a certain domain/subdomain. If no results are found from the query then the email will not be accepted.
@@ -569,10 +569,9 @@ DROP PROCEDURE IF EXISTS `check_access`$$
 CREATE PROCEDURE `check_access`(alias_email VARCHAR(254) charset utf8)
 BEGIN
     DECLARE no_alias_exists int(1);
-    DECLARE alias_action varchar(7) charset utf8;
-    DECLARE username_action varchar(7) charset utf8;
-    DECLARE additional_username_action varchar(7) charset utf8;
-    DECLARE domain_action varchar(7) charset utf8;
+    DECLARE alias_action varchar(30) charset utf8;
+    DECLARE username_action varchar(30) charset utf8;
+    DECLARE domain_action varchar(30) charset utf8;
     DECLARE alias_domain varchar(254) charset utf8;
 
     SET alias_domain = SUBSTRING_INDEX(alias_email, '@', -1);
@@ -587,7 +586,7 @@ BEGIN
             SET alias_action = (SELECT
                 IF(deleted_at IS NULL,
                 'DISCARD',
-                'REJECT')
+                'REJECT Address does not exist')
             FROM
                 aliases
             WHERE
@@ -597,7 +596,7 @@ BEGIN
         END IF;
 
         # If the alias is deactivated or deleted then increment its blocked count and return the alias_action
-        IF alias_action IN('DISCARD','REJECT') THEN
+        IF alias_action IN('DISCARD','REJECT Address does not exist') THEN
             UPDATE
                 aliases
             SET
@@ -612,45 +611,32 @@ BEGIN
             SELECT
                 CASE
                     WHEN no_alias_exists
-                    AND catch_all = 0 THEN "REJECT"
-                    ELSE NULL
-                END
-            FROM
-                users
-            WHERE
-                alias_domain IN ( CONCAT(username, '.example.com')) ),
-            (
-            SELECT
-                CASE
-                    WHEN no_alias_exists
-                    AND catch_all = 0 THEN "REJECT"
+                    AND catch_all = 0 THEN "REJECT Address does not exist"
                     WHEN active = 0 THEN "DISCARD"
                     ELSE NULL
                 END
             FROM
-                additional_usernames
+                usernames
             WHERE
                 alias_domain IN ( CONCAT(username, '.example.com')) ),
             (
             SELECT
                 CASE
                     WHEN no_alias_exists
-                    AND catch_all = 0 THEN "REJECT"
+                    AND catch_all = 0 THEN "REJECT Address does not exist"
                     WHEN active = 0 THEN "DISCARD"
                     ELSE NULL
                 END
             FROM
                 domains
             WHERE
-                domain = alias_domain) INTO username_action, additional_username_action, domain_action;
+                domain = alias_domain) INTO username_action, domain_action;
 
             # If all actions are NULL then we can return 'DUNNO' which will prevent Postfix from trying substrings of the alias
-            IF username_action IS NULL AND additional_username_action IS NULL AND domain_action IS NULL THEN
+            IF username_action IS NULL AND domain_action IS NULL THEN
                 SELECT 'DUNNO';
-            ELSEIF username_action IN('DISCARD','REJECT') THEN
+            ELSEIF username_action IN('DISCARD','REJECT Address does not exist') THEN
                 SELECT username_action;
-            ELSEIF additional_username_action IN('DISCARD','REJECT') THEN
-                SELECT additional_username_action;
             ELSE
                 SELECT domain_action;
             END IF;

+ 13 - 5
app/Console/Commands/CreateUser.php

@@ -4,9 +4,11 @@ namespace App\Console\Commands;
 
 use App\Models\Recipient;
 use App\Models\User;
+use App\Models\Username;
 use App\Rules\NotDeletedUsername;
 use App\Rules\NotLocalRecipient;
 use App\Rules\RegisterUniqueRecipient;
+use Illuminate\Auth\Events\Registered;
 use Illuminate\Console\Command;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Facades\Validator;
@@ -52,8 +54,7 @@ class CreateUser extends Command
                 'required',
                 'regex:/^[a-zA-Z0-9]*$/',
                 'max:20',
-                'unique:users,username',
-                'unique:additional_usernames,username',
+                'unique:usernames,username',
                 new NotDeletedUsername
             ],
             'email' => [
@@ -79,18 +80,25 @@ class CreateUser extends Command
             'user_id' => $userId
         ]);
 
+        $username = Username::create([
+            'username' => $this->argument('username'),
+            'user_id' => $userId
+        ]);
+
         $twoFactor = app('pragmarx.google2fa');
 
         $user = User::create([
             'id' => $userId,
-            'username' => $this->argument('username'),
+            'default_username_id' => $username->id,
             'default_recipient_id' => $recipient->id,
             'password' => Hash::make($userId),
             'two_factor_secret' => $twoFactor->generateSecretKey()
         ]);
 
-        $this->info('Created user: '.$user->username.' with userid: '.$user->id);
-        $this->info('This user can now reset their password (the default password is their guserid)');
+        event(new Registered($user));
+
+        $this->info('Created user: "'.$user->username.'" with user_id: "'.$user->id.'"');
+        $this->info('This user can now reset their password (the default password is their user_id)');
 
         return 0;
     }

+ 19 - 14
app/Console/Commands/ListUsers.php

@@ -15,7 +15,6 @@ class ListUsers extends Command
      * @var string
      */
     protected $signature = 'anonaddy:list-users
-                            {--columns=* : The columns to return}
                             {--username= : The Username of the user}
                             {--json : Output as JSON}
                             {--sort= : The column to sort by}';
@@ -67,21 +66,30 @@ class ListUsers extends Command
      */
     protected function getUsers()
     {
-        if ($columns = $this->option('columns')) {
-            $users = User::select($columns);
-        } else {
-            $users = User::select($this->getColumns());
-        }
+        $users = User::with('defaultUsername:id,user_id,username')
+            ->select(['id', 'default_username_id', 'bandwidth', 'created_at', 'updated_at']);
 
         if ($username = $this->option('username')) {
-            $users->where('username', $username);
+            $users->whereHas('usernames', function ($query) use ($username) {
+                $query->where('username', $username);
+            });
         }
 
+        $users = $users->get()->map(function ($user) {
+            return [
+                'id' => $user->id,
+                'username' => $user->defaultUsername->username,
+                'bandwidth' => $user->bandwidth,
+                'created_at' => $user->created_at,
+                'updated_at' => $user->updated_at
+            ];
+        });
+
         if ($sort = $this->option('sort')) {
-            $users->orderBy($sort);
+            $users = $users->sortBy($sort);
         }
 
-        return $users->get()->toArray();
+        return $users->toArray();
     }
 
     /**
@@ -118,11 +126,8 @@ class ListUsers extends Command
      */
     protected function getColumns()
     {
-        $availableColumns = array_map('strtolower', $this->headers);
-
-        if ($columns = $this->option('columns')) {
-            return array_intersect($availableColumns, $this->parseColumns($columns));
-        }
+        $availableColumns = collect($this->headers)
+            ->map(fn ($header) => strtolower($header))->toArray();
 
         return $availableColumns;
     }

+ 11 - 16
app/Console/Commands/ReceiveEmail.php

@@ -5,13 +5,12 @@ namespace App\Console\Commands;
 use App\Mail\ForwardEmail;
 use App\Mail\ReplyToEmail;
 use App\Mail\SendFromEmail;
-use App\Models\AdditionalUsername;
 use App\Models\Alias;
 use App\Models\Domain;
 use App\Models\EmailData;
 use App\Models\PostfixQueueId;
 use App\Models\Recipient;
-use App\Models\User;
+use App\Models\Username;
 use App\Notifications\DisallowedReplySendAttempt;
 use App\Notifications\FailedDeliveryNotification;
 use App\Notifications\NearBandwidthLimit;
@@ -96,7 +95,7 @@ class ReceiveEmail extends Command
                         $aliasable = $alias->aliasable;
                     }
                 } else {
-                    // Does not exist, must be a standard, additional username or custom domain alias
+                    // Does not exist, must be a standard, username or custom domain alias
                     $parentDomain = collect(config('anonaddy.all_domains'))
                     ->filter(function ($name) use ($recipient) {
                         return Str::endsWith($recipient['domain'], $name);
@@ -104,7 +103,7 @@ class ReceiveEmail extends Command
                     ->first();
 
                     if (!empty($parentDomain)) {
-                        // It is standard or additional username alias
+                        // It is standard or username alias
                         $subdomain = substr($recipient['domain'], 0, strrpos($recipient['domain'], '.' . $parentDomain)); // e.g. johndoe
 
                         if ($subdomain === 'unsubscribe') {
@@ -112,15 +111,11 @@ class ReceiveEmail extends Command
                             continue;
                         }
 
-                        // Check if this is an additional username or standard alias
+                        // Check if this is an username or standard alias
                         if (!empty($subdomain)) {
-                            $user = User::where('username', $subdomain)->first();
-
-                            if (!isset($user)) {
-                                $additionalUsername = AdditionalUsername::where('username', $subdomain)->first();
-                                $user = $additionalUsername->user;
-                                $aliasable = $additionalUsername;
-                            }
+                            $username = Username::where('username', $subdomain)->first();
+                            $user = $username->user;
+                            $aliasable = $username;
                         }
                     } else {
                         // It is a custom domain
@@ -131,7 +126,7 @@ class ReceiveEmail extends Command
                     }
 
                     if (!isset($user) && !empty(config('anonaddy.admin_username'))) {
-                        $user = User::where('username', config('anonaddy.admin_username'))->first();
+                        $user = Username::where('username', config('anonaddy.admin_username'))->first()?->user;
                     }
                 }
 
@@ -430,16 +425,16 @@ class ReceiveEmail extends Command
             exit(1);
         }
 
-        if ($user->nearBandwidthLimit() && ! Cache::has("user:{$user->username}:near-bandwidth")) {
+        if ($user->nearBandwidthLimit() && ! Cache::has("user:{$user->id}:near-bandwidth")) {
             $user->notify(new NearBandwidthLimit());
 
-            Cache::put("user:{$user->username}:near-bandwidth", now()->toDateTimeString(), now()->addDay());
+            Cache::put("user:{$user->id}:near-bandwidth", now()->toDateTimeString(), now()->addDay());
         }
     }
 
     protected function checkRateLimit($user)
     {
-        \Illuminate\Support\Facades\Redis::throttle("user:{$user->username}:limit:emails")
+        \Illuminate\Support\Facades\Redis::throttle("user:{$user->id}:limit:emails")
             ->allow(config('anonaddy.limit'))
             ->every(3600)
             ->then(

+ 5 - 5
app/Http/Controllers/Api/ActiveAdditionalUsernameController.php → app/Http/Controllers/Api/ActiveUsernameController.php

@@ -3,25 +3,25 @@
 namespace App\Http\Controllers\Api;
 
 use App\Http\Controllers\Controller;
-use App\Http\Resources\AdditionalUsernameResource;
+use App\Http\Resources\UsernameResource;
 use Illuminate\Http\Request;
 
-class ActiveAdditionalUsernameController extends Controller
+class ActiveUsernameController extends Controller
 {
     public function store(Request $request)
     {
         $request->validate(['id' => 'required|string']);
 
-        $username = user()->additionalUsernames()->findOrFail($request->id);
+        $username = user()->usernames()->findOrFail($request->id);
 
         $username->activate();
 
-        return new AdditionalUsernameResource($username);
+        return new UsernameResource($username);
     }
 
     public function destroy($id)
     {
-        $username = user()->additionalUsernames()->findOrFail($id);
+        $username = user()->usernames()->findOrFail($id);
 
         $username->deactivate();
 

+ 0 - 54
app/Http/Controllers/Api/AdditionalUsernameController.php

@@ -1,54 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\Api;
-
-use App\Http\Controllers\Controller;
-use App\Http\Requests\StoreAdditionalUsernameRequest;
-use App\Http\Requests\UpdateAdditionalUsernameRequest;
-use App\Http\Resources\AdditionalUsernameResource;
-
-class AdditionalUsernameController extends Controller
-{
-    public function index()
-    {
-        return AdditionalUsernameResource::collection(user()->additionalUsernames()->with(['aliases', 'defaultRecipient'])->latest()->get());
-    }
-
-    public function show($id)
-    {
-        $username = user()->additionalUsernames()->findOrFail($id);
-
-        return new AdditionalUsernameResource($username->load(['aliases', 'defaultRecipient']));
-    }
-
-    public function store(StoreAdditionalUsernameRequest $request)
-    {
-        if (user()->hasReachedAdditionalUsernameLimit()) {
-            return response('', 403);
-        }
-
-        $username = user()->additionalUsernames()->create(['username' => $request->username]);
-
-        user()->increment('username_count');
-
-        return new AdditionalUsernameResource($username->refresh()->load(['aliases', 'defaultRecipient']));
-    }
-
-    public function update(UpdateAdditionalUsernameRequest $request, $id)
-    {
-        $username = user()->additionalUsernames()->findOrFail($id);
-
-        $username->update(['description' => $request->description]);
-
-        return new AdditionalUsernameResource($username->refresh()->load(['aliases', 'defaultRecipient']));
-    }
-
-    public function destroy($id)
-    {
-        $username = user()->additionalUsernames()->findOrFail($id);
-
-        $username->delete();
-
-        return response('', 204);
-    }
-}

+ 0 - 25
app/Http/Controllers/Api/AdditionalUsernameDefaultRecipientController.php

@@ -1,25 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\Api;
-
-use App\Http\Controllers\Controller;
-use App\Http\Requests\UpdateAdditionalUsernameDefaultRecipientRequest;
-use App\Http\Resources\AdditionalUsernameResource;
-
-class AdditionalUsernameDefaultRecipientController extends Controller
-{
-    public function update(UpdateAdditionalUsernameDefaultRecipientRequest $request, $id)
-    {
-        $additionalUsername = user()->additionalUsernames()->findOrFail($id);
-        if (empty($request->default_recipient)) {
-            $additionalUsername->default_recipient_id = null;
-        } else {
-            $recipient = user()->verifiedRecipients()->findOrFail($request->default_recipient);
-            $additionalUsername->default_recipient = $recipient;
-        }
-
-        $additionalUsername->save();
-
-        return new AdditionalUsernameResource($additionalUsername);
-    }
-}

+ 4 - 4
app/Http/Controllers/Api/AliasController.php

@@ -7,8 +7,8 @@ use App\Http\Requests\IndexAliasRequest;
 use App\Http\Requests\StoreAliasRequest;
 use App\Http\Requests\UpdateAliasRequest;
 use App\Http\Resources\AliasResource;
-use App\Models\AdditionalUsername;
 use App\Models\Domain;
+use App\Models\Username;
 use Illuminate\Support\Str;
 use Ramsey\Uuid\Uuid;
 
@@ -106,7 +106,7 @@ class AliasController extends Controller
         }
 
 
-        // Check if domain is for additional username or custom domain
+        // Check if domain is for username or custom domain
         $parentDomain = collect(config('anonaddy.all_domains'))
                     ->filter(function ($name) use ($request) {
                         return Str::endsWith($request->domain, $name);
@@ -119,8 +119,8 @@ class AliasController extends Controller
         if ($parentDomain) {
             $subdomain = substr($request->domain, 0, strrpos($request->domain, '.'.$parentDomain));
 
-            if ($additionalUsername = AdditionalUsername::where('username', $subdomain)->first()) {
-                $aliasable = $additionalUsername;
+            if ($username = Username::where('username', $subdomain)->first()) {
+                $aliasable = $username;
             }
         } else {
             if ($customDomain = Domain::where('domain', $request->domain)->first()) {

+ 5 - 5
app/Http/Controllers/Api/CatchAllAdditionalUsernameController.php → app/Http/Controllers/Api/CatchAllUsernameController.php

@@ -3,25 +3,25 @@
 namespace App\Http\Controllers\Api;
 
 use App\Http\Controllers\Controller;
-use App\Http\Resources\AdditionalUsernameResource;
+use App\Http\Resources\UsernameResource;
 use Illuminate\Http\Request;
 
-class CatchAllAdditionalUsernameController extends Controller
+class CatchAllUsernameController extends Controller
 {
     public function store(Request $request)
     {
         $request->validate(['id' => 'required|string']);
 
-        $username = user()->additionalUsernames()->findOrFail($request->id);
+        $username = user()->usernames()->findOrFail($request->id);
 
         $username->enableCatchAll();
 
-        return new AdditionalUsernameResource($username);
+        return new UsernameResource($username);
     }
 
     public function destroy($id)
     {
-        $username = user()->additionalUsernames()->findOrFail($id);
+        $username = user()->usernames()->findOrFail($id);
 
         $username->disableCatchAll();
 

+ 1 - 1
app/Http/Controllers/Api/RecipientController.php

@@ -43,7 +43,7 @@ class RecipientController extends Controller
     public function destroy($id)
     {
         if ($id === user()->default_recipient_id) {
-            return response('', 403);
+            return response('You cannot delete your default recipient', 403);
         }
 
         $recipient = user()->recipients()->findOrFail($id);

+ 58 - 0
app/Http/Controllers/Api/UsernameController.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\StoreUsernameRequest;
+use App\Http\Requests\UpdateUsernameRequest;
+use App\Http\Resources\UsernameResource;
+
+class UsernameController extends Controller
+{
+    public function index()
+    {
+        return UsernameResource::collection(user()->usernames()->with(['aliases', 'defaultRecipient'])->latest()->get());
+    }
+
+    public function show($id)
+    {
+        $username = user()->usernames()->findOrFail($id);
+
+        return new UsernameResource($username->load(['aliases', 'defaultRecipient']));
+    }
+
+    public function store(StoreUsernameRequest $request)
+    {
+        if (user()->hasReachedUsernameLimit()) {
+            return response('', 403);
+        }
+
+        $username = user()->usernames()->create(['username' => $request->username]);
+
+        user()->increment('username_count');
+
+        return new UsernameResource($username->refresh()->load(['aliases', 'defaultRecipient']));
+    }
+
+    public function update(UpdateUsernameRequest $request, $id)
+    {
+        $username = user()->usernames()->findOrFail($id);
+
+        $username->update(['description' => $request->description]);
+
+        return new UsernameResource($username->refresh()->load(['aliases', 'defaultRecipient']));
+    }
+
+    public function destroy($id)
+    {
+        if ($id === user()->default_username_id) {
+            return response('You cannot delete your default username', 403);
+        }
+
+        $username = user()->usernames()->findOrFail($id);
+
+        $username->delete();
+
+        return response('', 204);
+    }
+}

+ 25 - 0
app/Http/Controllers/Api/UsernameDefaultRecipientController.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\UpdateUsernameDefaultRecipientRequest;
+use App\Http\Resources\UsernameResource;
+
+class UsernameDefaultRecipientController extends Controller
+{
+    public function update(UpdateUsernameDefaultRecipientRequest $request, $id)
+    {
+        $username = user()->usernames()->findOrFail($id);
+        if (empty($request->default_recipient)) {
+            $username->default_recipient_id = null;
+        } else {
+            $recipient = user()->verifiedRecipients()->findOrFail($request->default_recipient);
+            $username->default_recipient = $recipient;
+        }
+
+        $username->save();
+
+        return new UsernameResource($username);
+    }
+}

+ 5 - 1
app/Http/Controllers/Auth/ForgotPasswordController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
 
 use App\Http\Controllers\Controller;
 use App\Models\User;
+use App\Models\Username;
 use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Password;
@@ -44,11 +45,14 @@ class ForgotPasswordController extends Controller
     {
         $this->validateUsername($request);
 
+        // Find the user_id and use that for the credentials
+        $userId = Username::firstWhere('username', $request->username)?->user_id;
+
         // We will send the password reset link to this user. Once we have attempted
         // to send the link, we will examine the response then see the message we
         // need to show to the user. Finally, we'll send out a proper response.
         $response = $this->broker()->sendResetLink(
-            $request->only('username')
+            ['id' => $userId]
         );
 
         return $response == Password::RESET_LINK_SENT

+ 29 - 1
app/Http/Controllers/Auth/LoginController.php

@@ -3,7 +3,10 @@
 namespace App\Http\Controllers\Auth;
 
 use App\Http\Controllers\Controller;
+use App\Models\Username;
 use Illuminate\Foundation\Auth\AuthenticatesUsers;
+use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
 
 class LoginController extends Controller
 {
@@ -39,6 +42,31 @@ class LoginController extends Controller
 
     public function username()
     {
-        return 'username';
+        $login = request()->input('username');
+
+        $user = Username::firstWhere('username', $login)?->user;
+
+        if ($user) {
+            $login = $user->id;
+        }
+
+        request()->merge(['id' => $login]);
+
+        return 'id';
+    }
+
+    /**
+     * Get the failed login response instance.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \Illuminate\Validation\ValidationException
+     */
+    protected function sendFailedLoginResponse(Request $request)
+    {
+        throw ValidationException::withMessages([
+            'username' => [trans('auth.failed')],
+        ]);
     }
 }

+ 8 - 3
app/Http/Controllers/Auth/RegisterController.php

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth;
 use App\Http\Controllers\Controller;
 use App\Models\Recipient;
 use App\Models\User;
+use App\Models\Username;
 use App\Rules\NotBlacklisted;
 use App\Rules\NotDeletedUsername;
 use App\Rules\NotLocalRecipient;
@@ -60,8 +61,7 @@ class RegisterController extends Controller
                 'required',
                 'regex:/^[a-zA-Z0-9]*$/',
                 'max:20',
-                'unique:users,username',
-                'unique:additional_usernames,username',
+                'unique:usernames,username',
                 new NotBlacklisted,
                 new NotDeletedUsername
             ],
@@ -97,11 +97,16 @@ class RegisterController extends Controller
             'user_id' => $userId
         ]);
 
+        $username = Username::create([
+            'username' => $data['username'],
+            'user_id' => $userId
+        ]);
+
         $twoFactor = app('pragmarx.google2fa');
 
         return User::create([
             'id' => $userId,
-            'username' => $data['username'],
+            'default_username_id' => $username->id,
             'default_recipient_id' => $recipient->id,
             'password' => Hash::make($data['password']),
             'two_factor_secret' => $twoFactor->generateSecretKey()

+ 10 - 3
app/Http/Controllers/Auth/ResetPasswordController.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers\Auth;
 
 use App\Http\Controllers\Controller;
+use App\Models\Username;
 use Illuminate\Foundation\Auth\ResetsPasswords;
 use Illuminate\Http\Request;
 
@@ -44,11 +45,12 @@ class ResetPasswordController extends Controller
      * If no token is present, display the link request form.
      *
      * @param  \Illuminate\Http\Request  $request
-     * @param  string|null  $token
      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
      */
-    public function showResetForm(Request $request, $token = null)
+    public function showResetForm(Request $request)
     {
+        $token = $request->route()->parameter('token');
+
         return view('auth.passwords.reset')->with(
             ['token' => $token, 'username' => $request->username]
         );
@@ -76,8 +78,13 @@ class ResetPasswordController extends Controller
      */
     protected function credentials(Request $request)
     {
+        // Find the user_id and use that for the credentials
+        $userId = Username::firstWhere('username', $request->username)?->user_id;
+
+        $request->merge(['id' => $userId]);
+
         return $request->only(
-            'username',
+            'id',
             'password',
             'password_confirmation',
             'token'

+ 0 - 19
app/Http/Controllers/CatchAllController.php

@@ -1,19 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Http\Requests\UpdateCatchAllRequest;
-
-class CatchAllController extends Controller
-{
-    public function update(UpdateCatchAllRequest $request)
-    {
-        if ($request->catch_all) {
-            user()->enableCatchAll();
-        } else {
-            user()->disableCatchAll();
-        }
-
-        return back()->with(['status' => $request->catch_all ? 'Catch-All Enabled Successfully' : 'Catch-All Disabled Successfully']);
-    }
-}

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

@@ -28,7 +28,9 @@ class DefaultRecipientController extends Controller
         user()->default_recipient = $recipient;
         user()->save();
 
-        $currentDefaultRecipient->notify(new DefaultRecipientUpdated($recipient->email));
+        if ($currentDefaultRecipient->id !== $recipient->id) {
+            $currentDefaultRecipient->notify(new DefaultRecipientUpdated($recipient->email));
+        }
 
         return back()->with(['status' => 'Default Recipient Updated Successfully']);
     }

+ 4 - 4
app/Http/Controllers/ShowRecipientController.php

@@ -9,7 +9,7 @@ class ShowRecipientController extends Controller
         $recipients = user()->recipients()->with([
             'aliases:id,aliasable_id,email',
             'domainsUsingAsDefault.aliases:id,aliasable_id,email',
-            'AdditionalUsernamesUsingAsDefault.aliases:id,aliasable_id,email'
+            'usernamesUsingAsDefault.aliases:id,aliasable_id,email'
         ])->latest()->get();
 
         $recipients->each(function ($recipient) {
@@ -20,11 +20,11 @@ class ShowRecipientController extends Controller
                 $recipient->setRelation('aliases', $recipient->aliases->concat($domainAliases)->unique('email'));
             }
 
-            if ($recipient->AdditionalUsernamesUsingAsDefault) {
-                $AdditionalUsernameAliases = $recipient->AdditionalUsernamesUsingAsDefault->flatMap(function ($domain) {
+            if ($recipient->usernamesUsingAsDefault) {
+                $usernameAliases = $recipient->usernamesUsingAsDefault->flatMap(function ($domain) {
                     return $domain->aliases;
                 });
-                $recipient->setRelation('aliases', $recipient->aliases->concat($AdditionalUsernameAliases)->unique('email'));
+                $recipient->setRelation('aliases', $recipient->aliases->concat($usernameAliases)->unique('email'));
             }
         });
 

+ 2 - 2
app/Http/Controllers/ShowAdditionalUsernameController.php → app/Http/Controllers/ShowUsernameController.php

@@ -2,13 +2,13 @@
 
 namespace App\Http\Controllers;
 
-class ShowAdditionalUsernameController extends Controller
+class ShowUsernameController extends Controller
 {
     public function index()
     {
         return view('usernames.index', [
             'usernames' => user()
-                ->additionalUsernames()
+                ->usernames()
                 ->with('defaultRecipient:id,email')
                 ->withCount('aliases')
                 ->latest()

+ 2 - 3
app/Http/Requests/StoreAdditionalUsernameRequest.php → app/Http/Requests/StoreUsernameRequest.php

@@ -6,7 +6,7 @@ use App\Rules\NotBlacklisted;
 use App\Rules\NotDeletedUsername;
 use Illuminate\Foundation\Http\FormRequest;
 
-class StoreAdditionalUsernameRequest extends FormRequest
+class StoreUsernameRequest extends FormRequest
 {
     /**
      * Determine if the user is authorized to make this request.
@@ -30,8 +30,7 @@ class StoreAdditionalUsernameRequest extends FormRequest
                 'required',
                 'regex:/^[a-zA-Z0-9]*$/',
                 'max:20',
-                'unique:users,username',
-                'unique:additional_usernames,username',
+                'unique:usernames,username',
                 new NotBlacklisted,
                 new NotDeletedUsername
             ],

+ 0 - 30
app/Http/Requests/UpdateCatchAllRequest.php

@@ -1,30 +0,0 @@
-<?php
-
-namespace App\Http\Requests;
-
-use Illuminate\Foundation\Http\FormRequest;
-
-class UpdateCatchAllRequest extends FormRequest
-{
-    /**
-     * Determine if the user is authorized to make this request.
-     *
-     * @return bool
-     */
-    public function authorize()
-    {
-        return true;
-    }
-
-    /**
-     * Get the validation rules that apply to the request.
-     *
-     * @return array
-     */
-    public function rules()
-    {
-        return [
-            'catch_all' => 'required|boolean'
-        ];
-    }
-}

+ 1 - 1
app/Http/Requests/UpdateAdditionalUsernameDefaultRecipientRequest.php → app/Http/Requests/UpdateUsernameDefaultRecipientRequest.php

@@ -4,7 +4,7 @@ namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
 
-class UpdateAdditionalUsernameDefaultRecipientRequest extends FormRequest
+class UpdateUsernameDefaultRecipientRequest extends FormRequest
 {
     /**
      * Determine if the user is authorized to make this request.

+ 1 - 1
app/Http/Requests/UpdateAdditionalUsernameRequest.php → app/Http/Requests/UpdateUsernameRequest.php

@@ -4,7 +4,7 @@ namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
 
-class UpdateAdditionalUsernameRequest extends FormRequest
+class UpdateUsernameRequest extends FormRequest
 {
     /**
      * Determine if the user is authorized to make this request.

+ 1 - 1
app/Http/Resources/AdditionalUsernameResource.php → app/Http/Resources/UsernameResource.php

@@ -4,7 +4,7 @@ namespace App\Http\Resources;
 
 use Illuminate\Http\Resources\Json\JsonResource;
 
-class AdditionalUsernameResource extends JsonResource
+class UsernameResource extends JsonResource
 {
     public function toArray($request)
     {

+ 3 - 6
app/Jobs/DeleteAccount.php

@@ -2,7 +2,6 @@
 
 namespace App\Jobs;
 
-use App\Models\DeletedUsername;
 use App\Models\User;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -34,11 +33,9 @@ class DeleteAccount implements ShouldQueue, ShouldBeEncrypted
      */
     public function handle()
     {
-        DeletedUsername::create(['username' => $this->user->username]);
-
         $this->user->aliasRecipients()->delete();
 
-        $sharedDomainAliases = $this->user->aliases()->whereIn('domain', config('anonaddy.all_domains'));
+        $sharedDomainAliases = $this->user->aliases()->withTrashed()->whereIn('domain', config('anonaddy.all_domains'));
         // Remove data from shared domain aliases
         $sharedDomainAliases->update([
             'extension' => null,
@@ -51,11 +48,11 @@ class DeleteAccount implements ShouldQueue, ShouldBeEncrypted
         // Soft delete any aliases at shared domains
         $sharedDomainAliases->delete();
         // Force delete any other aliases
-        $this->user->aliases()->whereNotIn('domain', config('anonaddy.all_domains'))->forceDelete();
+        $this->user->aliases()->withTrashed()->whereNotIn('domain', config('anonaddy.all_domains'))->forceDelete();
 
         $this->user->recipients()->get()->each->delete(); // In order to fire deleting model event.
         $this->user->domains()->delete();
-        $this->user->additionalUsernames()->get()->each->delete(); // In order to fire deleting model event.
+        $this->user->usernames()->get()->each->delete(); // In order to fire deleting model event.
         $this->user->tokens()->delete();
         $this->user->rules()->delete();
         $this->user->webauthnKeys()->delete();

+ 1 - 1
app/Models/Alias.php

@@ -130,7 +130,7 @@ class Alias extends Model
     public function verifiedRecipientsOrDefault()
     {
         if ($this->verifiedRecipients()->count() === 0) {
-            // If the alias is for a custom domain or additional username that has a default recipient set.
+            // If the alias is for a custom domain or username that has a default recipient set.
             if (isset($this->aliasable->defaultRecipient)) {
                 return $this->aliasable->defaultRecipient();
             }

+ 2 - 2
app/Models/Recipient.php

@@ -106,9 +106,9 @@ class Recipient extends Model
     /**
      * Get all of the user's custom domains.
      */
-    public function additionalUsernamesUsingAsDefault()
+    public function usernamesUsingAsDefault()
     {
-        return $this->hasMany(AdditionalUsername::class, 'default_recipient_id', 'id');
+        return $this->hasMany(Username::class, 'default_recipient_id', 'id');
     }
 
     /**

+ 34 - 17
app/Models/User.php

@@ -29,7 +29,6 @@ class User extends Authenticatable implements MustVerifyEmail
      */
     protected $fillable = [
         'id',
-        'username',
         'from_name',
         'email_subject',
         'banner_location',
@@ -38,6 +37,7 @@ class User extends Authenticatable implements MustVerifyEmail
         'default_alias_domain',
         'default_alias_format',
         'use_reply_to',
+        'default_username_id',
         'default_recipient_id',
         'password',
         'two_factor_enabled',
@@ -70,6 +70,7 @@ class User extends Authenticatable implements MustVerifyEmail
      */
     protected $casts = [
         'id' => 'string',
+        'default_username_id' => 'string',
         'default_recipient_id' => 'string',
         'catch_all' => 'boolean',
         'two_factor_enabled' => 'boolean',
@@ -82,14 +83,6 @@ class User extends Authenticatable implements MustVerifyEmail
         'email_verified_at'
     ];
 
-    /**
-     * Set the user's username.
-     */
-    public function setUsernameAttribute($value)
-    {
-        $this->attributes['username'] = strtolower($value);
-    }
-
     /**
      * Get the user's default email.
      */
@@ -114,6 +107,23 @@ class User extends Authenticatable implements MustVerifyEmail
         $this->defaultRecipient->update(['email_verified_at' => $value]);
     }
 
+    /**
+     * Get the user's default username.
+     */
+    public function getUsernameAttribute()
+    {
+        return $this->defaultUsername->username;
+    }
+
+    /**
+     * Set the user's default username.
+     */
+    public function setDefaultUsernameAttribute($username)
+    {
+        $this->attributes['default_username_id'] = $username->id;
+        $this->setRelation('defaultUsername', $username);
+    }
+
     /**
      * Set the user's default email.
      */
@@ -131,6 +141,14 @@ class User extends Authenticatable implements MustVerifyEmail
         return round($this->bandwidth / 1024 / 1024, 2);
     }
 
+    /**
+     * Get the user's default username.
+     */
+    public function defaultUsername()
+    {
+        return $this->hasOne(Username::class, 'id', 'default_username_id');
+    }
+
     /**
      * Get the user's default recipient.
      */
@@ -220,11 +238,11 @@ class User extends Authenticatable implements MustVerifyEmail
     }
 
     /**
-     * Get all of the user's additional usernames.
+     * Get all of the user's usernames.
      */
-    public function additionalUsernames()
+    public function Usernames()
     {
-        return $this->hasMany(AdditionalUsername::class);
+        return $this->hasMany(Username::class);
     }
 
     /**
@@ -283,7 +301,7 @@ class User extends Authenticatable implements MustVerifyEmail
         return $this->aliases()->whereDoesntHave('recipients')->where(function (Builder $q) {
             return $q->whereDoesntHaveMorph(
                 'aliasable',
-                ['App\Models\Domain', 'App\Models\AdditionalUsername'],
+                ['App\Models\Domain', 'App\Models\Username'],
                 function (Builder $query) {
                     $query->whereNotNull('default_recipient_id');
                 }
@@ -368,7 +386,7 @@ class User extends Authenticatable implements MustVerifyEmail
             return false;
         }
 
-        return \Illuminate\Support\Facades\Redis::throttle("user:{$this->username}:limit:new-alias")
+        return \Illuminate\Support\Facades\Redis::throttle("user:{$this->id}:limit:new-alias")
             ->allow(config('anonaddy.new_alias_hourly_limit'))
             ->every(3600)
             ->then(
@@ -381,7 +399,7 @@ class User extends Authenticatable implements MustVerifyEmail
             );
     }
 
-    public function hasReachedAdditionalUsernameLimit()
+    public function hasReachedUsernameLimit()
     {
         return $this->username_count >= config('anonaddy.additional_username_limit');
     }
@@ -483,9 +501,8 @@ class User extends Authenticatable implements MustVerifyEmail
 
         $allDomains = config('anonaddy.all_domains')[0] ? config('anonaddy.all_domains') : [config('anonaddy.domain')];
 
-        return $this->additionalUsernames()
+        return $this->usernames()
             ->pluck('username')
-            ->push($this->username)
             ->flatMap(function ($username) use ($allDomains) {
                 return collect($allDomains)->map(function ($domain) use ($username) {
                     return $username.'.'.$domain;

+ 7 - 6
app/Models/AdditionalUsername.php → app/Models/Username.php

@@ -7,7 +7,7 @@ use App\Traits\HasUuid;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
-class AdditionalUsername extends Model
+class Username extends Model
 {
     use HasUuid, HasEncryptedAttributes, HasFactory;
 
@@ -20,6 +20,7 @@ class AdditionalUsername extends Model
     ];
 
     protected $fillable = [
+        'user_id',
         'username',
         'description',
         'active',
@@ -43,7 +44,7 @@ class AdditionalUsername extends Model
     {
         parent::boot();
 
-        AdditionalUsername::deleting(function ($username) {
+        Username::deleting(function ($username) {
             $username->aliases()->withTrashed()->forceDelete();
             DeletedUsername::create(['username' => $username->username]);
         });
@@ -58,7 +59,7 @@ class AdditionalUsername extends Model
     }
 
     /**
-     * Get the user for the additional username.
+     * Get the user for the username.
      */
     public function user()
     {
@@ -66,7 +67,7 @@ class AdditionalUsername extends Model
     }
 
     /**
-     * Get all of the additional usernames's aliases.
+     * Get all of the usernames's aliases.
      */
     public function aliases()
     {
@@ -74,14 +75,14 @@ class AdditionalUsername extends Model
     }
 
     /**
-     * Get the additional usernames's default recipient.
+     * Get the usernames's default recipient.
      */
     public function defaultRecipient()
     {
         return $this->hasOne(Recipient::class, 'id', 'default_recipient_id');
     }
     /**
-     * Set the additional usernames's default recipient.
+     * Set the usernames's default recipient.
      */
     public function setDefaultRecipientAttribute($recipient)
     {

+ 117 - 114
composer.lock

@@ -960,16 +960,16 @@
         },
         {
             "name": "doctrine/lexer",
-            "version": "1.2.2",
+            "version": "1.2.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/lexer.git",
-                "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c"
+                "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/lexer/zipball/9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c",
-                "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c",
+                "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229",
+                "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229",
                 "shasum": ""
             },
             "require": {
@@ -977,7 +977,7 @@
             },
             "require-dev": {
                 "doctrine/coding-standard": "^9.0",
-                "phpstan/phpstan": "1.3",
+                "phpstan/phpstan": "^1.3",
                 "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
                 "vimeo/psalm": "^4.11"
             },
@@ -1016,7 +1016,7 @@
             ],
             "support": {
                 "issues": "https://github.com/doctrine/lexer/issues",
-                "source": "https://github.com/doctrine/lexer/tree/1.2.2"
+                "source": "https://github.com/doctrine/lexer/tree/1.2.3"
             },
             "funding": [
                 {
@@ -1032,7 +1032,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-12T08:27:12+00:00"
+            "time": "2022-02-28T11:07:21+00:00"
         },
         {
             "name": "dragonmantank/cron-expression",
@@ -1182,12 +1182,12 @@
             },
             "type": "library",
             "autoload": {
-                "psr-0": {
-                    "HTMLPurifier": "library/"
-                },
                 "files": [
                     "library/HTMLPurifier.composer.php"
                 ],
+                "psr-0": {
+                    "HTMLPurifier": "library/"
+                },
                 "exclude-from-classmap": [
                     "/library/HTMLPurifier/Language/"
                 ]
@@ -2526,16 +2526,16 @@
         },
         {
             "name": "league/commonmark",
-            "version": "2.2.2",
+            "version": "2.2.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/commonmark.git",
-                "reference": "13d7751377732637814f0cda0e3f6d3243f9f769"
+                "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/13d7751377732637814f0cda0e3f6d3243f9f769",
-                "reference": "13d7751377732637814f0cda0e3f6d3243f9f769",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71",
+                "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71",
                 "shasum": ""
             },
             "require": {
@@ -2626,7 +2626,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-02-13T15:00:57+00:00"
+            "time": "2022-02-26T21:24:45+00:00"
         },
         {
             "name": "league/config",
@@ -3170,16 +3170,16 @@
         },
         {
             "name": "maatwebsite/excel",
-            "version": "3.1.36",
+            "version": "3.1.37",
             "source": {
                 "type": "git",
                 "url": "https://github.com/SpartnerNL/Laravel-Excel.git",
-                "reference": "eb31f30d72c51c3fb11644b636945accbe50404f"
+                "reference": "49ccd4142d3d7bce492d6bfb9dd9a27b12935408"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/eb31f30d72c51c3fb11644b636945accbe50404f",
-                "reference": "eb31f30d72c51c3fb11644b636945accbe50404f",
+                "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/49ccd4142d3d7bce492d6bfb9dd9a27b12935408",
+                "reference": "49ccd4142d3d7bce492d6bfb9dd9a27b12935408",
                 "shasum": ""
             },
             "require": {
@@ -3232,7 +3232,7 @@
             ],
             "support": {
                 "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
-                "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.36"
+                "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.37"
             },
             "funding": [
                 {
@@ -3244,7 +3244,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-01-27T18:34:20+00:00"
+            "time": "2022-02-28T10:20:04+00:00"
         },
         {
             "name": "maennchen/zipstream-php",
@@ -4060,12 +4060,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Opis\\Closure\\": "src/"
-                },
                 "files": [
                     "functions.php"
-                ]
+                ],
+                "psr-4": {
+                    "Opis\\Closure\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -4878,8 +4878,8 @@
             },
             "autoload": {
                 "psr-4": {
-                    "PragmaRX\\Version\\Package\\": "src/package",
-                    "PragmaRX\\Version\\Tests\\": "tests/"
+                    "PragmaRX\\Version\\Tests\\": "tests/",
+                    "PragmaRX\\Version\\Package\\": "src/package"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -5382,16 +5382,16 @@
         },
         {
             "name": "psy/psysh",
-            "version": "v0.11.1",
+            "version": "v0.11.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/bobthecow/psysh.git",
-                "reference": "570292577277f06f590635381a7f761a6cf4f026"
+                "reference": "7f7da640d68b9c9fec819caae7c744a213df6514"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/570292577277f06f590635381a7f761a6cf4f026",
-                "reference": "570292577277f06f590635381a7f761a6cf4f026",
+                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7f7da640d68b9c9fec819caae7c744a213df6514",
+                "reference": "7f7da640d68b9c9fec819caae7c744a213df6514",
                 "shasum": ""
             },
             "require": {
@@ -5402,6 +5402,9 @@
                 "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4",
                 "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4"
             },
+            "conflict": {
+                "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4"
+            },
             "require-dev": {
                 "bamarni/composer-bin-plugin": "^1.2",
                 "hoa/console": "3.17.05.02"
@@ -5451,9 +5454,9 @@
             ],
             "support": {
                 "issues": "https://github.com/bobthecow/psysh/issues",
-                "source": "https://github.com/bobthecow/psysh/tree/v0.11.1"
+                "source": "https://github.com/bobthecow/psysh/tree/v0.11.2"
             },
-            "time": "2022-01-03T13:58:38+00:00"
+            "time": "2022-02-28T15:28:54+00:00"
         },
         {
             "name": "ralouphie/getallheaders",
@@ -5643,12 +5646,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Ramsey\\Uuid\\": "src/"
-                },
                 "files": [
                     "src/functions.php"
-                ]
+                ],
+                "psr-4": {
+                    "Ramsey\\Uuid\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -5899,16 +5902,16 @@
         },
         {
             "name": "symfony/console",
-            "version": "v5.4.3",
+            "version": "v5.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "a2a86ec353d825c75856c6fd14fac416a7bdb6b8"
+                "reference": "d8111acc99876953f52fe16d4c50eb60940d49ad"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/a2a86ec353d825c75856c6fd14fac416a7bdb6b8",
-                "reference": "a2a86ec353d825c75856c6fd14fac416a7bdb6b8",
+                "url": "https://api.github.com/repos/symfony/console/zipball/d8111acc99876953f52fe16d4c50eb60940d49ad",
+                "reference": "d8111acc99876953f52fe16d4c50eb60940d49ad",
                 "shasum": ""
             },
             "require": {
@@ -5978,7 +5981,7 @@
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v5.4.3"
+                "source": "https://github.com/symfony/console/tree/v5.4.5"
             },
             "funding": [
                 {
@@ -5994,7 +5997,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-26T16:28:35+00:00"
+            "time": "2022-02-24T12:45:35+00:00"
         },
         {
             "name": "symfony/css-selector",
@@ -6426,16 +6429,16 @@
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v5.4.3",
+            "version": "v5.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "ef409ff341a565a3663157d4324536746d49a0c7"
+                "reference": "dd68a3b24262a902bc338fc7c9a2a61b7ab2029f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ef409ff341a565a3663157d4324536746d49a0c7",
-                "reference": "ef409ff341a565a3663157d4324536746d49a0c7",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/dd68a3b24262a902bc338fc7c9a2a61b7ab2029f",
+                "reference": "dd68a3b24262a902bc338fc7c9a2a61b7ab2029f",
                 "shasum": ""
             },
             "require": {
@@ -6479,7 +6482,7 @@
             "description": "Defines an object-oriented layer for the HTTP specification",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-foundation/tree/v5.4.3"
+                "source": "https://github.com/symfony/http-foundation/tree/v5.4.5"
             },
             "funding": [
                 {
@@ -6495,20 +6498,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-02T09:53:40+00:00"
+            "time": "2022-02-21T15:00:19+00:00"
         },
         {
             "name": "symfony/http-kernel",
-            "version": "v5.4.4",
+            "version": "v5.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-kernel.git",
-                "reference": "49f40347228c773688a0488feea0175aa7f4d268"
+                "reference": "c770c90bc71f1db911e2d996c991fdafe273ac84"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/49f40347228c773688a0488feea0175aa7f4d268",
-                "reference": "49f40347228c773688a0488feea0175aa7f4d268",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c770c90bc71f1db911e2d996c991fdafe273ac84",
+                "reference": "c770c90bc71f1db911e2d996c991fdafe273ac84",
                 "shasum": ""
             },
             "require": {
@@ -6591,7 +6594,7 @@
             "description": "Provides a structured process for converting a Request into a Response",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-kernel/tree/v5.4.4"
+                "source": "https://github.com/symfony/http-kernel/tree/v5.4.5"
             },
             "funding": [
                 {
@@ -6607,7 +6610,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-29T18:08:07+00:00"
+            "time": "2022-02-28T07:57:55+00:00"
         },
         {
             "name": "symfony/mime",
@@ -6726,12 +6729,12 @@
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
-                },
                 "files": [
                     "bootstrap.php"
-                ]
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -7511,16 +7514,16 @@
         },
         {
             "name": "symfony/process",
-            "version": "v5.4.3",
+            "version": "v5.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "553f50487389a977eb31cf6b37faae56da00f753"
+                "reference": "95440409896f90a5f85db07a32b517ecec17fa4c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/553f50487389a977eb31cf6b37faae56da00f753",
-                "reference": "553f50487389a977eb31cf6b37faae56da00f753",
+                "url": "https://api.github.com/repos/symfony/process/zipball/95440409896f90a5f85db07a32b517ecec17fa4c",
+                "reference": "95440409896f90a5f85db07a32b517ecec17fa4c",
                 "shasum": ""
             },
             "require": {
@@ -7553,7 +7556,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v5.4.3"
+                "source": "https://github.com/symfony/process/tree/v5.4.5"
             },
             "funding": [
                 {
@@ -7569,7 +7572,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-26T16:28:35+00:00"
+            "time": "2022-01-30T18:16:22+00:00"
         },
         {
             "name": "symfony/psr-http-message-bridge",
@@ -7863,12 +7866,12 @@
             },
             "type": "library",
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\String\\": ""
-                },
                 "files": [
                     "Resources/functions.php"
                 ],
+                "psr-4": {
+                    "Symfony\\Component\\String\\": ""
+                },
                 "exclude-from-classmap": [
                     "/Tests/"
                 ]
@@ -7918,16 +7921,16 @@
         },
         {
             "name": "symfony/translation",
-            "version": "v6.0.3",
+            "version": "v6.0.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "71bb15335798f8c4da110911bcf2d2fead7a430d"
+                "reference": "e69501c71107cc3146b32aaa45f4edd0c3427875"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/71bb15335798f8c4da110911bcf2d2fead7a430d",
-                "reference": "71bb15335798f8c4da110911bcf2d2fead7a430d",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/e69501c71107cc3146b32aaa45f4edd0c3427875",
+                "reference": "e69501c71107cc3146b32aaa45f4edd0c3427875",
                 "shasum": ""
             },
             "require": {
@@ -7993,7 +7996,7 @@
             "description": "Provides tools to internationalize your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/translation/tree/v6.0.3"
+                "source": "https://github.com/symfony/translation/tree/v6.0.5"
             },
             "funding": [
                 {
@@ -8009,7 +8012,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-07T00:29:03+00:00"
+            "time": "2022-02-09T15:52:48+00:00"
         },
         {
             "name": "symfony/translation-contracts",
@@ -8091,16 +8094,16 @@
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v5.4.3",
+            "version": "v5.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "970a01f208bf895c5f327ba40b72288da43adec4"
+                "reference": "6efddb1cf6af5270b21c48c6103e81f920c220f0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/970a01f208bf895c5f327ba40b72288da43adec4",
-                "reference": "970a01f208bf895c5f327ba40b72288da43adec4",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6efddb1cf6af5270b21c48c6103e81f920c220f0",
+                "reference": "6efddb1cf6af5270b21c48c6103e81f920c220f0",
                 "shasum": ""
             },
             "require": {
@@ -8160,7 +8163,7 @@
                 "dump"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v5.4.3"
+                "source": "https://github.com/symfony/var-dumper/tree/v5.4.5"
             },
             "funding": [
                 {
@@ -8176,7 +8179,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-17T16:30:37+00:00"
+            "time": "2022-02-21T15:00:19+00:00"
         },
         {
             "name": "symfony/yaml",
@@ -9092,30 +9095,30 @@
         },
         {
             "name": "composer/pcre",
-            "version": "1.0.1",
+            "version": "3.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/pcre.git",
-                "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560"
+                "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/pcre/zipball/67a32d7d6f9f560b726ab25a061b38ff3a80c560",
-                "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560",
+                "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd",
+                "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.2 || ^7.0 || ^8.0"
+                "php": "^7.4 || ^8.0"
             },
             "require-dev": {
                 "phpstan/phpstan": "^1.3",
                 "phpstan/phpstan-strict-rules": "^1.1",
-                "symfony/phpunit-bridge": "^4.2 || ^5"
+                "symfony/phpunit-bridge": "^5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.x-dev"
+                    "dev-main": "3.x-dev"
                 }
             },
             "autoload": {
@@ -9143,7 +9146,7 @@
             ],
             "support": {
                 "issues": "https://github.com/composer/pcre/issues",
-                "source": "https://github.com/composer/pcre/tree/1.0.1"
+                "source": "https://github.com/composer/pcre/tree/3.0.0"
             },
             "funding": [
                 {
@@ -9159,7 +9162,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-21T20:24:37+00:00"
+            "time": "2022-02-25T20:21:48+00:00"
         },
         {
             "name": "composer/semver",
@@ -9244,20 +9247,20 @@
         },
         {
             "name": "composer/xdebug-handler",
-            "version": "3.0.1",
+            "version": "3.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "12f1b79476638a5615ed00ea6adbb269cec96fd8"
+                "reference": "ced299686f41dce890debac69273b47ffe98a40c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/12f1b79476638a5615ed00ea6adbb269cec96fd8",
-                "reference": "12f1b79476638a5615ed00ea6adbb269cec96fd8",
+                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c",
+                "reference": "ced299686f41dce890debac69273b47ffe98a40c",
                 "shasum": ""
             },
             "require": {
-                "composer/pcre": "^1",
+                "composer/pcre": "^1 || ^2 || ^3",
                 "php": "^7.2.5 || ^8.0",
                 "psr/log": "^1 || ^2 || ^3"
             },
@@ -9290,7 +9293,7 @@
             "support": {
                 "irc": "irc://irc.freenode.org/composer",
                 "issues": "https://github.com/composer/xdebug-handler/issues",
-                "source": "https://github.com/composer/xdebug-handler/tree/3.0.1"
+                "source": "https://github.com/composer/xdebug-handler/tree/3.0.3"
             },
             "funding": [
                 {
@@ -9306,7 +9309,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-04T18:29:42+00:00"
+            "time": "2022-02-25T21:32:43+00:00"
         },
         {
             "name": "doctrine/annotations",
@@ -10529,16 +10532,16 @@
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "9.2.13",
+            "version": "9.2.14",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "deac8540cb7bd40b2b8cfa679b76202834fd04e8"
+                "reference": "9f4d60b6afe5546421462b76cd4e633ebc364ab4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/deac8540cb7bd40b2b8cfa679b76202834fd04e8",
-                "reference": "deac8540cb7bd40b2b8cfa679b76202834fd04e8",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f4d60b6afe5546421462b76cd4e633ebc364ab4",
+                "reference": "9f4d60b6afe5546421462b76cd4e633ebc364ab4",
                 "shasum": ""
             },
             "require": {
@@ -10594,7 +10597,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.13"
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.14"
             },
             "funding": [
                 {
@@ -10602,7 +10605,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-02-23T17:02:38+00:00"
+            "time": "2022-02-28T12:38:02+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
@@ -11914,16 +11917,16 @@
         },
         {
             "name": "symfony/filesystem",
-            "version": "v6.0.3",
+            "version": "v6.0.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "6ae49c4fda17322171a2b8dc5f70bc6edbc498e1"
+                "reference": "6646c13f787057d64701a3a0235cf9567c6ccbbd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/6ae49c4fda17322171a2b8dc5f70bc6edbc498e1",
-                "reference": "6ae49c4fda17322171a2b8dc5f70bc6edbc498e1",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/6646c13f787057d64701a3a0235cf9567c6ccbbd",
+                "reference": "6646c13f787057d64701a3a0235cf9567c6ccbbd",
                 "shasum": ""
             },
             "require": {
@@ -11957,7 +11960,7 @@
             "description": "Provides basic utilities for the filesystem",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/filesystem/tree/v6.0.3"
+                "source": "https://github.com/symfony/filesystem/tree/v6.0.5"
             },
             "funding": [
                 {
@@ -11973,7 +11976,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-02T09:55:41+00:00"
+            "time": "2022-02-28T07:42:30+00:00"
         },
         {
             "name": "symfony/options-resolver",
@@ -12044,16 +12047,16 @@
         },
         {
             "name": "symfony/stopwatch",
-            "version": "v6.0.3",
+            "version": "v6.0.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/stopwatch.git",
-                "reference": "6835045bb9f00fa4486ea4f1bcaf623be761556f"
+                "reference": "f2c1780607ec6502f2121d9729fd8150a655d337"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6835045bb9f00fa4486ea4f1bcaf623be761556f",
-                "reference": "6835045bb9f00fa4486ea4f1bcaf623be761556f",
+                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f2c1780607ec6502f2121d9729fd8150a655d337",
+                "reference": "f2c1780607ec6502f2121d9729fd8150a655d337",
                 "shasum": ""
             },
             "require": {
@@ -12086,7 +12089,7 @@
             "description": "Provides a way to profile code",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/stopwatch/tree/v6.0.3"
+                "source": "https://github.com/symfony/stopwatch/tree/v6.0.5"
             },
             "funding": [
                 {
@@ -12102,7 +12105,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-02T09:55:41+00:00"
+            "time": "2022-02-21T17:15:17+00:00"
         },
         {
             "name": "theseer/tokenizer",

+ 3 - 3
config/version.yml

@@ -4,10 +4,10 @@ current:
   label: v
   major: 0
   minor: 10
-  patch: 0
-  prerelease: 1-g1efe2ee
+  patch: 1
+  prerelease: 1-gaca8a64
   buildmetadata: ''
-  commit: 1efe2e
+  commit: aca8a6
   timestamp:
     year: 2020
     month: 10

+ 2 - 2
database/factories/UserFactory.php

@@ -4,6 +4,7 @@ namespace Database\Factories;
 
 use App\Models\Recipient;
 use App\Models\User;
+use App\Models\Username;
 use Illuminate\Database\Eloquent\Factories\Factory;
 use Illuminate\Support\Str;
 
@@ -24,10 +25,9 @@ class UserFactory extends Factory
     public function definition()
     {
         return [
-            'username' => $this->faker->userName.$this->faker->randomNumber(3),
+            'default_username_id' => Username::factory(['username' => $this->faker->userName.$this->faker->randomNumber(3)]),
             'banner_location' => 'top',
             'bandwidth' => 0,
-            'catch_all' => 1,
             'default_recipient_id' => Recipient::factory(),
             'use_reply_to' => 0,
             'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret

+ 3 - 3
database/factories/AdditionalUsernameFactory.php → database/factories/UsernameFactory.php

@@ -2,17 +2,17 @@
 
 namespace Database\Factories;
 
-use App\Models\AdditionalUsername;
+use App\Models\Username;
 use Illuminate\Database\Eloquent\Factories\Factory;
 
-class AdditionalUsernameFactory extends Factory
+class UsernameFactory extends Factory
 {
     /**
      * The name of the factory's corresponding model.
      *
      * @var string
      */
-    protected $model = AdditionalUsername::class;
+    protected $model = Username::class;
 
     /**
      * Define the model's default state.

+ 131 - 0
database/migrations/2022_02_25_091005_move_account_username_to_usernames_table.php

@@ -0,0 +1,131 @@
+<?php
+
+use App\Models\Alias;
+use App\Models\User;
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class MoveAccountUsernameToUsernamesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        // Rename the additional_usernames table to usernames
+        Schema::rename('additional_usernames', 'usernames');
+
+        // Update unique index name
+        Schema::table('usernames', function (Blueprint $table) {
+            $table->renameIndex('additional_usernames_username_unique', 'usernames_username_unique');
+        });
+
+        // Add default_username_id to users table
+        Schema::table('users', function (Blueprint $table) {
+            $table->uuid('default_username_id')->nullable()->after('id');
+        });
+
+        // Add current username from users table to the usernames table
+        User::select(['id', 'username', 'default_username_id', 'catch_all'])->chunkById(200, function ($users) {
+            foreach ($users as $user) {
+                $username = $user->usernames()->create([
+                    'username' => $user->getAttributes()['username'],
+                    'catch_all' => $user->catch_all
+                ]);
+
+                $user->update(['default_username_id' => $username->id]);
+
+                // Update all aliases using that username
+                $allDomains = config('anonaddy.all_domains')[0] ? config('anonaddy.all_domains') : [config('anonaddy.domain')];
+
+                $usernameSubdomains = collect($allDomains)->map(function ($domain) use ($user) {
+                    return $user->getAttributes()['username'].'.'.$domain;
+                })->toArray();
+
+                $user->aliases()
+                    ->withTrashed()
+                    ->select(['id', 'user_id', 'aliasable_id', 'aliasable_type', 'domain'])
+                    ->whereIn('domain', $usernameSubdomains)
+                    ->update([
+                        'aliasable_id' => $username->id,
+                        'aliasable_type' => 'App\Models\Username'
+                    ]);
+            }
+        });
+
+        // Remove nullable from default_username_id column
+        if (User::whereNull('default_username_id')->count() === 0) {
+            Schema::table('users', function (Blueprint $table) {
+                $table->uuid('default_username_id')->nullable(false)->change();
+            });
+        }
+
+        // Update all additional username aliases aliasable_type
+        Alias::withTrashed()
+        ->where('aliasable_type', 'App\Models\AdditionalUsername')
+        ->update(['aliasable_type' => 'App\Models\Username']);
+
+        // Drop the username and catch_all column from the users table
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('username');
+        });
+        // Separate call to dropColumn since SQLite doesn't support multiple calls
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('catch_all');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        // Add the username column back to the users table
+        Schema::table('users', function (Blueprint $table) {
+            $table->string('username')->after('id');
+            $table->boolean('catch_all')->after('banner_location')->default(true);
+        });
+
+        // Update all usernames back to additional username
+        Alias::withTrashed()
+        ->where('aliasable_type', 'App\Models\Username')
+        ->update(['aliasable_type' => 'App\Models\AdditionalUsername']);
+
+        // Repopulate the username column and delete the defaultUsername from the usernames table
+        User::select(['id', 'username', 'default_username_id', 'catch_all'])->chunk(200, function ($users) {
+            foreach ($users as $user) {
+                // Revert all aliases using that username
+                $user->aliases()->withTrashed()->where('aliasable_id', $user->default_username_id)->update(['aliasable_id' => null, 'aliasable_type' => null]);
+
+                $user->setRawAttributes(['username' => $user->defaultUsername->username]);
+                $user->catch_all = $user->defaultUsername->catch_all;
+                $user->save();
+
+                $user->defaultUsername->delete();
+            }
+        });
+
+        // Add the unique index back
+        Schema::table('users', function (Blueprint $table) {
+            $table->unique('username');
+        });
+
+        // Drop the default_username_id column
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('default_username_id');
+        });
+
+        // Rename the usernames table back to additional_usernames
+        Schema::rename('usernames', 'additional_usernames');
+
+        // Update unique index name
+        Schema::table('additional_usernames', function (Blueprint $table) {
+            $table->renameIndex('usernames_username_unique', 'additional_usernames_username_unique');
+        });
+    }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 298 - 298
package-lock.json


+ 1 - 1
package.json

@@ -14,7 +14,7 @@
     },
     "dependencies": {
       "autoprefixer": "^10.4.1",
-        "axios": "^0.24.0",
+        "axios": "^0.26.0",
         "cross-env": "^7.0.3",
         "dayjs": "^1.10.4",
         "laravel-mix": "^6.0.11",

+ 1 - 1
resources/js/pages/Aliases.vue

@@ -401,7 +401,7 @@
           this:
         </p>
         <p class="mb-4">
-          <b>86064c92-da41-443e-a2bf-5a7b0247842f@{{ domain }}</b>
+          <b>x481n904@{{ domain }}</b>
         </p>
         <p>
           Useful if you do not wish to include your username in the email as a potential link

+ 25 - 12
resources/js/pages/Usernames.vue

@@ -67,6 +67,12 @@
             v-clipboard:error="clipboardError"
             >{{ props.row.username | truncate(30) }}</span
           >
+          <span
+            v-if="isDefault(props.row.id)"
+            class="ml-3 py-1 px-2 text-sm bg-yellow-200 text-yellow-900 rounded-full"
+          >
+            default
+          </span>
         </span>
         <span v-else-if="props.column.field == 'description'">
           <div v-if="usernameIdToEdit === props.row.id" class="flex items-center">
@@ -151,6 +157,7 @@
         </span>
         <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
           <icon
+            v-if="!isDefault(props.row.id)"
             name="trash"
             class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
             @click.native="openDeleteModal(props.row.id)"
@@ -162,12 +169,12 @@
     <div v-else class="bg-white rounded shadow overflow-x-auto">
       <div class="p-8 text-center text-lg text-grey-700">
         <h1 class="mb-6 text-xl text-indigo-800 font-semibold">
-          This is where you can add and view additional usernames
+          This is where you can add and view usernames
         </h1>
         <div class="mx-auto mb-6 w-24 border-b-2 border-grey-200"></div>
         <p class="mb-4">
-          When you add an additional username here you will be able to use it exactly like the
-          username you signed up with!
+          When you add a username here you will be able to use it exactly like the username you
+          signed up with!
         </p>
         <p class="mb-4">
           You can then separate aliases under your different usernames to reduce the chance of
@@ -175,8 +182,8 @@
           and personal emails.
         </p>
         <p>
-          You can add a maximum of {{ usernameCount }} additional usernames. Deleted usernames still
-          count towards your limit so please choose carefully.
+          You can add a maximum of {{ usernameCount }} usernames. Deleted usernames still count
+          towards your limit so please choose carefully.
         </p>
       </div>
     </div>
@@ -189,9 +196,8 @@
           Add new username
         </h2>
         <p class="mt-4 text-grey-700">
-          Please choose additional usernames carefully as you can only add a maximum of
-          {{ usernameCount }}. You cannot login with these usernames, only the one you originally
-          signed up with.
+          Please choose usernames carefully as you can only add a maximum of
+          {{ usernameCount }}. You can login with any of your usernames.
         </p>
         <div class="mt-6">
           <p v-show="errors.newUsername" class="mb-3 text-red-500 text-sm">
@@ -282,7 +288,7 @@
         <p class="mt-4 text-grey-700">
           Are you sure you want to delete this username? This will also delete all aliases
           associated with this username. You will no longer be able to receive any emails at this
-          username subdomain. This will <b>still count</b> towards your additional username limit
+          username subdomain. This will <b>still count</b> towards your username limit
           <b>even once deleted</b>.
         </p>
         <div class="mt-6">
@@ -319,6 +325,10 @@ import Multiselect from 'vue-multiselect'
 
 export default {
   props: {
+    user: {
+      type: Object,
+      required: true,
+    },
     initialUsernames: {
       type: Array,
       required: true,
@@ -438,6 +448,9 @@ export default {
 
       e.preventDefault()
     },
+    isDefault(id) {
+      return this.user.default_username_id === id
+    },
     addNewUsername() {
       this.addUsernameLoading = true
 
@@ -456,13 +469,13 @@ export default {
           this.rows.push(data.data)
           this.newUsername = ''
           this.addUsernameModalOpen = false
-          this.success('Additional Username added')
+          this.success('Username added')
         })
         .catch(error => {
           this.addUsernameLoading = false
 
           if (error.response.status === 403) {
-            this.error('You have reached your additional username limit')
+            this.error('You have reached your username limit')
           } else if (error.response.status == 422) {
             this.error(error.response.data.errors.username[0])
           } else {
@@ -533,7 +546,7 @@ export default {
           this.usernameDefaultRecipientModalOpen = false
           this.editDefaultRecipientLoading = false
           this.defaultRecipient = {}
-          this.success("Additional Username's default recipient updated")
+          this.success("Username's default recipient updated")
         })
         .catch(error => {
           this.usernameDefaultRecipientModalOpen = false

+ 0 - 45
resources/views/settings/show.blade.php

@@ -548,51 +548,6 @@
 
         <div class="px-6 py-8 md:p-10 bg-white rounded-lg shadow mb-10">
 
-            <form class="mb-16" method="POST" action="{{ route('settings.catch_all') }}">
-                @csrf
-
-                <div class="mb-6">
-
-                    <h3 class="font-bold text-xl">
-                        Update Catch-All Functionality for Account Username
-                    </h3>
-
-                    <div class="mt-4 w-24 border-b-2 border-grey-200"></div>
-
-                    <p class="mt-6">This will determine if your main account username (<b>{{ $user->username }}</b>) is able to function as a catch-all subdomain. When enabled you will be able to create any alias at {{ $user->username }}.{{ config('anonaddy.domain') }} or any of your other subdomains on-the-fly. Meaning they will be created automatically in your dashboard as soon as they receive their first email.
-                    </p>
-                    <p class="mt-4">When disabled you will only be able to receive email for your unique username subdomains if an alias <b>already exists</b> in your account.</p>
-
-                    <div class="mt-6 flex flex-wrap mb-4">
-                        <label for="catch_all" class="block text-grey-700 text-sm mb-2">
-                            {{ __('Update Catch-All') }}:
-                        </label>
-
-                        <div class="block relative w-full">
-                            <select id="catch_all" class="block appearance-none w-full text-grey-700 bg-grey-100 p-3 pr-8 rounded shadow focus:ring" name="catch_all" required>
-                                <option value="1" {{ $user->catch_all ? 'selected' : '' }}>Enabled</option>
-                                <option value="0" {{ ! $user->catch_all ? 'selected' : '' }}>Disabled</option>
-                            </select>
-                            <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
-                                <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
-                            </div>
-                        </div>
-
-                        @if ($errors->has('catch_all'))
-                            <p class="text-red-500 text-xs italic mt-4">
-                                {{ $errors->first('catch_all') }}
-                            </p>
-                        @endif
-                    </div>
-
-                </div>
-
-                <button type="submit" class="bg-cyan-400 w-full hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none">
-                    Update Username Catch-All
-                </button>
-
-            </form>
-
             <form class="mb-16" method="POST" action="{{ route('settings.from_name') }}">
                 @csrf
 

+ 1 - 1
resources/views/usernames/index.blade.php

@@ -4,6 +4,6 @@
     <div class="container py-8">
         @include('shared.status')
 
-        <usernames :initial-usernames="{{json_encode($usernames)}}" :username-count="{{config('anonaddy.additional_username_limit')}}" :recipient-options="{{ json_encode(Auth::user()->verifiedRecipients) }}" />
+        <usernames :user="{{json_encode(Auth::user())}}" :initial-usernames="{{json_encode($usernames)}}" :username-count="{{config('anonaddy.additional_username_limit')}}" :recipient-options="{{ json_encode(Auth::user()->verifiedRecipients) }}" />
     </div>
 @endsection

+ 12 - 12
routes/api.php

@@ -57,18 +57,18 @@ Route::group([
     Route::post('/catch-all-domains', 'Api\CatchAllDomainController@store');
     Route::delete('/catch-all-domains/{id}', 'Api\CatchAllDomainController@destroy');
 
-    Route::get('/usernames', 'Api\AdditionalUsernameController@index');
-    Route::get('/usernames/{id}', 'Api\AdditionalUsernameController@show');
-    Route::post('/usernames', 'Api\AdditionalUsernameController@store');
-    Route::patch('/usernames/{id}', 'Api\AdditionalUsernameController@update');
-    Route::delete('/usernames/{id}', 'Api\AdditionalUsernameController@destroy');
-    Route::patch('/usernames/{id}/default-recipient', 'Api\AdditionalUsernameDefaultRecipientController@update');
-
-    Route::post('/active-usernames', 'Api\ActiveAdditionalUsernameController@store');
-    Route::delete('/active-usernames/{id}', 'Api\ActiveAdditionalUsernameController@destroy');
-
-    Route::post('/catch-all-usernames', 'Api\CatchAllAdditionalUsernameController@store');
-    Route::delete('/catch-all-usernames/{id}', 'Api\CatchAllAdditionalUsernameController@destroy');
+    Route::get('/usernames', 'Api\UsernameController@index');
+    Route::get('/usernames/{id}', 'Api\UsernameController@show');
+    Route::post('/usernames', 'Api\UsernameController@store');
+    Route::patch('/usernames/{id}', 'Api\UsernameController@update');
+    Route::delete('/usernames/{id}', 'Api\UsernameController@destroy');
+    Route::patch('/usernames/{id}/default-recipient', 'Api\UsernameDefaultRecipientController@update');
+
+    Route::post('/active-usernames', 'Api\ActiveUsernameController@store');
+    Route::delete('/active-usernames/{id}', 'Api\ActiveUsernameController@destroy');
+
+    Route::post('/catch-all-usernames', 'Api\CatchAllUsernameController@store');
+    Route::delete('/catch-all-usernames/{id}', 'Api\CatchAllUsernameController@destroy');
 
     Route::get('/rules', 'Api\RuleController@index');
     Route::get('/rules/{id}', 'Api\RuleController@show');

+ 1 - 3
routes/web.php

@@ -45,7 +45,7 @@ Route::middleware(['auth', 'verified', '2fa', 'webauthn'])->group(function () {
     Route::get('/domains', 'ShowDomainController@index')->name('domains.index');
     Route::get('/domains/{id}/check-sending', 'DomainVerificationController@checkSending');
 
-    Route::get('/usernames', 'ShowAdditionalUsernameController@index')->name('usernames.index');
+    Route::get('/usernames', 'ShowUsernameController@index')->name('usernames.index');
 
     Route::get('/deactivate/{alias}', 'DeactivateAliasController@deactivate')->name('deactivate');
 
@@ -75,8 +75,6 @@ Route::group([
 
     Route::post('/banner-location', 'BannerLocationController@update')->name('settings.banner_location');
 
-    Route::post('/catch-all', 'CatchAllController@update')->name('settings.catch_all');
-
     Route::post('/use-reply-to', 'UseReplyToController@update')->name('settings.use_reply_to');
 
     Route::post('/password', 'PasswordController@update')->name('settings.password');

+ 7 - 2
tests/Feature/Api/AliasesTest.php

@@ -2,10 +2,10 @@
 
 namespace Tests\Feature\Api;
 
-use App\Models\AdditionalUsername;
 use App\Models\Alias;
 use App\Models\Domain;
 use App\Models\Recipient;
+use App\Models\Username;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Tests\TestCase;
 
@@ -17,6 +17,11 @@ class AliasesTest extends TestCase
     {
         parent::setUp();
         parent::setUpPassport();
+        $this->user->update(['username' => 'johndoe']);
+        $this->user->recipients()->save($this->user->defaultRecipient);
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */
@@ -237,7 +242,7 @@ class AliasesTest extends TestCase
     /** @test */
     public function user_can_generate_new_alias_with_correct_aliasable_type()
     {
-        AdditionalUsername::factory()->create([
+        Username::factory()->create([
             'user_id' => $this->user->id,
             'username' => 'john'
         ]);

+ 4 - 0
tests/Feature/Api/ApiTokensTest.php

@@ -23,6 +23,10 @@ class ApiTokensTest extends TestCase
         Passport::actingAs($this->user, [], 'web');
         $this->user->recipients()->save($this->user->defaultRecipient);
 
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
+
         $clientRepository = new ClientRepository();
         $client = $clientRepository->createPersonalAccessClient(
             null,

+ 3 - 0
tests/Feature/Api/FailedDeliveriesTest.php

@@ -17,6 +17,9 @@ class FailedDeliveriesTest extends TestCase
 
         $this->user->update(['username' => 'johndoe']);
         $this->user->recipients()->save($this->user->defaultRecipient);
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */

+ 3 - 0
tests/Feature/Api/RulesTest.php

@@ -22,6 +22,9 @@ class RulesTest extends TestCase
 
         $this->user->update(['username' => 'johndoe']);
         $this->user->recipients()->save($this->user->defaultRecipient);
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */

+ 60 - 46
tests/Feature/Api/AdditionalUsernamesTest.php → tests/Feature/Api/UsernamesTest.php

@@ -2,14 +2,14 @@
 
 namespace Tests\Feature\Api;
 
-use App\Models\AdditionalUsername;
 use App\Models\DeletedUsername;
 use App\Models\Recipient;
 use App\Models\User;
+use App\Models\Username;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Tests\TestCase;
 
-class AdditionalUsernamesTest extends TestCase
+class UsernamesTest extends TestCase
 {
     use RefreshDatabase;
 
@@ -20,10 +20,10 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_get_all_additional_usernames()
+    public function user_can_get_all_usernames()
     {
         // Arrange
-        AdditionalUsername::factory()->count(3)->create([
+        Username::factory()->count(3)->create([
             'user_id' => $this->user->id
         ]);
 
@@ -36,10 +36,10 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_get_individual_additional_username()
+    public function user_can_get_individual_username()
     {
         // Arrange
-        $username = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id
         ]);
 
@@ -53,7 +53,7 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_create_additional_username()
+    public function user_can_create_username()
     {
         $response = $this->json('POST', '/api/v1/usernames', [
             'username' => 'janedoe'
@@ -65,7 +65,7 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_not_exceed_additional_username_limit()
+    public function user_can_not_exceed_username_limit()
     {
         $this->json('POST', '/api/v1/usernames', [
             'username' => 'username1'
@@ -85,13 +85,13 @@ class AdditionalUsernamesTest extends TestCase
 
         $response->assertStatus(403);
         $this->assertEquals(3, $this->user->username_count);
-        $this->assertCount(3, $this->user->additionalUsernames);
+        $this->assertCount(3, $this->user->usernames);
     }
 
     /** @test */
     public function user_can_not_create_the_same_username()
     {
-        AdditionalUsername::factory()->create([
+        Username::factory()->create([
             'user_id' => $this->user->id,
             'username' => 'janedoe'
         ]);
@@ -106,7 +106,7 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_not_create_additional_username_that_has_been_deleted()
+    public function user_can_not_create_username_that_has_been_deleted()
     {
         DeletedUsername::factory()->create([
             'username' => 'janedoe'
@@ -122,7 +122,7 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function must_be_unique_across_users_and_additional_usernames_tables()
+    public function must_be_unique_across_users_and_usernames_tables()
     {
         $user = User::factory()->create();
 
@@ -136,7 +136,7 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function additional_username_must_be_alpha_numeric()
+    public function username_must_be_alpha_numeric()
     {
         $response = $this->json('POST', '/api/v1/usernames', [
             'username' => 'username01_'
@@ -148,7 +148,7 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function additional_username_must_be_less_than_max_length()
+    public function username_must_be_less_than_max_length()
     {
         $response = $this->json('POST', '/api/v1/usernames', [
             'username' => 'abcdefghijklmnopqrstu'
@@ -160,9 +160,9 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_activate_additional_username()
+    public function user_can_activate_username()
     {
-        $username = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id,
             'active' => false
         ]);
@@ -176,9 +176,9 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_deactivate_additional_username()
+    public function user_can_deactivate_username()
     {
-        $username = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id,
             'active' => true
         ]);
@@ -186,13 +186,13 @@ class AdditionalUsernamesTest extends TestCase
         $response = $this->json('DELETE', '/api/v1/active-usernames/'.$username->id);
 
         $response->assertStatus(204);
-        $this->assertFalse($this->user->additionalUsernames[0]->active);
+        $this->assertFalse($this->user->usernames[0]->active);
     }
 
     /** @test */
-    public function user_can_enable_catch_all_for_additional_username()
+    public function user_can_enable_catch_all_for_username()
     {
-        $username = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id,
             'catch_all' => false
         ]);
@@ -206,9 +206,9 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_disable_catch_all_for_additional_username()
+    public function user_can_disable_catch_all_for_username()
     {
-        $username = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id,
             'catch_all' => true
         ]);
@@ -216,13 +216,13 @@ class AdditionalUsernamesTest extends TestCase
         $response = $this->json('DELETE', '/api/v1/catch-all-usernames/'.$username->id);
 
         $response->assertStatus(204);
-        $this->assertFalse($this->user->additionalUsernames[0]->catch_all);
+        $this->assertFalse($this->user->usernames[0]->catch_all);
     }
 
     /** @test */
-    public function user_can_update_additional_usernames_description()
+    public function user_can_update_usernames_description()
     {
-        $username = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id
         ]);
 
@@ -235,24 +235,38 @@ class AdditionalUsernamesTest extends TestCase
     }
 
     /** @test */
-    public function user_can_delete_additional_username()
+    public function user_can_delete_username()
     {
-        $username = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id
         ]);
 
         $response = $this->json('DELETE', '/api/v1/usernames/'.$username->id);
 
         $response->assertStatus(204);
-        $this->assertEmpty($this->user->additionalUsernames);
+        $this->assertEmpty($this->user->usernames);
 
         $this->assertEquals(DeletedUsername::first()->username, $username->username);
     }
 
     /** @test */
-    public function user_can_update_additional_username_default_recipient()
+    public function user_can_not_delete_default_username()
     {
-        $additionalUsername = AdditionalUsername::factory()->create([
+        $this->user->usernames()->save($this->user->defaultUsername);
+
+        $defaultUsername = $this->user->defaultUsername;
+
+        $response = $this->json('DELETE', '/api/v1/usernames/'.$defaultUsername->id);
+
+        $response->assertStatus(403);
+        $this->assertCount(1, $this->user->usernames);
+        $this->assertEquals($defaultUsername->id, $this->user->defaultUsername->id);
+    }
+
+    /** @test */
+    public function user_can_update_username_default_recipient()
+    {
+        $username = Username::factory()->create([
             'user_id' => $this->user->id
         ]);
 
@@ -260,23 +274,23 @@ class AdditionalUsernamesTest extends TestCase
             'user_id' => $this->user->id
         ]);
 
-        $response = $this->json('PATCH', '/api/v1/usernames/'.$additionalUsername->id.'/default-recipient', [
+        $response = $this->json('PATCH', '/api/v1/usernames/'.$username->id.'/default-recipient', [
             'default_recipient' => $newDefaultRecipient->id
         ]);
 
         $response->assertStatus(200);
-        $this->assertDatabaseHas('additional_usernames', [
-            'id' => $additionalUsername->id,
+        $this->assertDatabaseHas('usernames', [
+            'id' => $username->id,
             'default_recipient_id' => $newDefaultRecipient->id
         ]);
 
-        $this->assertEquals($newDefaultRecipient->email, $additionalUsername->refresh()->defaultRecipient->email);
+        $this->assertEquals($newDefaultRecipient->email, $username->refresh()->defaultRecipient->email);
     }
 
     /** @test */
-    public function user_cannot_update_additional_username_default_recipient_with_unverified_recipient()
+    public function user_cannot_update_username_default_recipient_with_unverified_recipient()
     {
-        $additionalUsername = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id
         ]);
 
@@ -285,39 +299,39 @@ class AdditionalUsernamesTest extends TestCase
             'email_verified_at' => null
         ]);
 
-        $response = $this->json('PATCH', '/api/v1/usernames/'.$additionalUsername->id.'/default-recipient', [
+        $response = $this->json('PATCH', '/api/v1/usernames/'.$username->id.'/default-recipient', [
             'default_recipient' => $newDefaultRecipient->id
         ]);
 
         $response->assertStatus(404);
-        $this->assertDatabaseMissing('additional_usernames', [
-            'id' => $additionalUsername->id,
+        $this->assertDatabaseMissing('usernames', [
+            'id' => $username->id,
             'default_recipient_id' => $newDefaultRecipient->id
         ]);
     }
 
     /** @test */
-    public function user_can_remove_additional_username_default_recipient()
+    public function user_can_remove_username_default_recipient()
     {
         $defaultRecipient = Recipient::factory()->create([
             'user_id' => $this->user->id
         ]);
 
-        $additionalUsername = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id,
             'default_recipient_id' => $defaultRecipient->id
         ]);
 
-        $response = $this->json('PATCH', '/api/v1/usernames/'.$additionalUsername->id.'/default-recipient', [
+        $response = $this->json('PATCH', '/api/v1/usernames/'.$username->id.'/default-recipient', [
             'default_recipient' => ''
         ]);
 
         $response->assertStatus(200);
-        $this->assertDatabaseHas('additional_usernames', [
-            'id' => $additionalUsername->id,
+        $this->assertDatabaseHas('usernames', [
+            'id' => $username->id,
             'default_recipient_id' => null
         ]);
 
-        $this->assertNull($additionalUsername->refresh()->defaultRecipient);
+        $this->assertNull($username->refresh()->defaultRecipient);
     }
 }

+ 22 - 1
tests/Feature/LoginTest.php

@@ -3,6 +3,7 @@
 namespace Tests\Feature;
 
 use App\Models\User;
+use App\Models\Username;
 use App\Notifications\UsernameReminder;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Support\Facades\Hash;
@@ -21,10 +22,13 @@ class LoginTest extends TestCase
         parent::setUp();
 
         $this->user = User::factory()->create([
-            'username' => 'johndoe',
             'password' => Hash::make('mypassword')
         ]);
         $this->user->recipients()->save($this->user->defaultRecipient);
+
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */
@@ -40,6 +44,23 @@ class LoginTest extends TestCase
             ->assertSessionHasNoErrors();
     }
 
+    /** @test */
+    public function user_can_login_with_any_username()
+    {
+        $username = Username::factory()->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $response = $this->post('/login', [
+            'username' => $username->username,
+            'password' => 'mypassword'
+        ]);
+
+        $response
+            ->assertRedirect('/')
+            ->assertSessionHasNoErrors();
+    }
+
     /** @test */
     public function user_can_login_successfully_using_backup_code()
     {

+ 17 - 14
tests/Feature/ReceiveEmailTest.php

@@ -3,12 +3,12 @@
 namespace Tests\Feature;
 
 use App\Mail\ForwardEmail;
-use App\Models\AdditionalUsername;
 use App\Models\Alias;
 use App\Models\AliasRecipient;
 use App\Models\Domain;
 use App\Models\Recipient;
 use App\Models\User;
+use App\Models\Username;
 use App\Notifications\NearBandwidthLimit;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Support\Facades\Cache;
@@ -26,8 +26,12 @@ class ReceiveEmailTest extends TestCase
     {
         parent::setUp();
 
-        $this->user = User::factory()->create(['username' => 'johndoe']);
+        $this->user = User::factory()->create();
         $this->user->recipients()->save($this->user->defaultRecipient);
+        $this->user->usernames()->save($this->user->defaultUsername);
+
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */
@@ -755,13 +759,13 @@ class ReceiveEmailTest extends TestCase
     }
 
     /** @test */
-    public function it_can_forward_email_for_additional_username()
+    public function it_can_forward_email_for_username()
     {
         Mail::fake();
 
         Mail::assertNothingSent();
 
-        $additionalUsername = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id,
             'username' => 'janedoe'
         ]);
@@ -769,7 +773,7 @@ class ReceiveEmailTest extends TestCase
         $this->artisan(
             'anonaddy:receive-email',
             [
-                'file' => base_path('tests/emails/email_additional_username.eml'),
+                'file' => base_path('tests/emails/email_username.eml'),
                 '--sender' => 'will@anonaddy.com',
                 '--recipient' => ['ebay@janedoe.anonaddy.com'],
                 '--local_part' => ['ebay'],
@@ -780,8 +784,8 @@ class ReceiveEmailTest extends TestCase
         )->assertExitCode(0);
 
         $this->assertDatabaseHas('aliases', [
-            'aliasable_id' => $additionalUsername->id,
-            'aliasable_type' => 'App\Models\AdditionalUsername',
+            'aliasable_id' => $username->id,
+            'aliasable_type' => 'App\Models\Username',
             'email' => 'ebay@janedoe.anonaddy.com',
             'local_part' => 'ebay',
             'domain' => 'janedoe.anonaddy.com',
@@ -838,7 +842,7 @@ class ReceiveEmailTest extends TestCase
 
         Notification::assertNothingSent();
 
-        Cache::put("user:{$this->user->username}:near-bandwidth", now()->toDateTimeString(), now()->addDay());
+        Cache::put("user:{$this->user->id}:near-bandwidth", now()->toDateTimeString(), now()->addDay());
 
         $this->user->update(['bandwidth' => 9485760]);
 
@@ -865,7 +869,6 @@ class ReceiveEmailTest extends TestCase
         $this->assertEquals(1, $this->user->aliases()->count());
         $this->assertDatabaseHas('users', [
             'id' => $this->user->id,
-            'username' => 'johndoe',
             'bandwidth' => '9486760'
         ]);
 
@@ -956,7 +959,7 @@ class ReceiveEmailTest extends TestCase
     }
 
     /** @test */
-    public function it_can_forward_email_for_additional_username_with_default_recipient()
+    public function it_can_forward_email_for_username_with_default_recipient()
     {
         Mail::fake();
 
@@ -966,7 +969,7 @@ class ReceiveEmailTest extends TestCase
             'user_id' => $this->user->id
         ]);
 
-        $additionalUsername = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id,
             'default_recipient_id' => $recipient->id,
             'username' => 'janedoe'
@@ -975,7 +978,7 @@ class ReceiveEmailTest extends TestCase
         $this->artisan(
             'anonaddy:receive-email',
             [
-                'file' => base_path('tests/emails/email_additional_username.eml'),
+                'file' => base_path('tests/emails/email_username.eml'),
                 '--sender' => 'will@anonaddy.com',
                 '--recipient' => ['ebay@janedoe.anonaddy.com'],
                 '--local_part' => ['ebay'],
@@ -986,8 +989,8 @@ class ReceiveEmailTest extends TestCase
         )->assertExitCode(0);
 
         $this->assertDatabaseHas('aliases', [
-            'aliasable_id' => $additionalUsername->id,
-            'aliasable_type' => 'App\Models\AdditionalUsername',
+            'aliasable_id' => $username->id,
+            'aliasable_type' => 'App\Models\Username',
             'email' => 'ebay@janedoe.anonaddy.com',
             'local_part' => 'ebay',
             'domain' => 'janedoe.anonaddy.com',

+ 14 - 20
tests/Feature/RegistrationTest.php

@@ -2,10 +2,10 @@
 
 namespace Tests\Feature;
 
-use App\Models\AdditionalUsername;
 use App\Models\DeletedUsername;
 use App\Models\Recipient;
 use App\Models\User;
+use App\Models\Username;
 use App\Notifications\CustomVerifyEmail;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Support\Carbon;
@@ -36,11 +36,11 @@ class RegistrationTest extends TestCase
             ->assertRedirect('/')
             ->assertSessionHasNoErrors();
 
-        $this->assertDatabaseHas('users', [
+        $this->assertDatabaseHas('usernames', [
             'username' => 'johndoe'
         ]);
 
-        $user = User::where('username', 'johndoe')->first();
+        $user = Username::where('username', 'johndoe')->first()->user;
 
         Notification::assertSentTo(
             $user,
@@ -130,15 +130,20 @@ class RegistrationTest extends TestCase
     /** @test */
     public function user_cannot_register_with_existing_email()
     {
-        $user = User::factory()->create(['username' => 'johndoe']);
+        $user = User::factory()->create();
 
         Recipient::factory()->create([
             'user_id' => $user->id,
             'email' => 'johndoe@example.com'
         ]);
 
+        Username::factory()->create([
+            'user_id' => $user->id,
+            'username' => 'johndoe'
+        ]);
+
         $response = $this->post('/register', [
-            'username' => 'johndoe',
+            'username' => 'janedoe',
             'email' => 'johndoe@example.com',
             'email_confirmation' => 'johndoe@example.com',
             'password' => 'mypassword',
@@ -151,24 +156,13 @@ class RegistrationTest extends TestCase
     /** @test */
     public function user_cannot_register_with_existing_username()
     {
-        User::factory()->create(['username' => 'johndoe']);
+        $user = User::factory()->create();
 
-        $response = $this->post('/register', [
-            'username' => 'johndoe',
-            'email' => 'johndoe@example.com',
-            'email_confirmation' => 'johndoe@example.com',
-            'password' => 'mypassword',
-            'terms' => true,
+        Username::factory()->create([
+            'user_id' => $user->id,
+            'username' => 'johndoe'
         ]);
 
-        $response->assertSessionHasErrors(['username']);
-    }
-
-    /** @test */
-    public function user_cannot_register_with_existing_additional_username()
-    {
-        AdditionalUsername::factory()->create(['username' => 'johndoe']);
-
         $response = $this->post('/register', [
             'username' => 'johndoe',
             'email' => 'johndoe@example.com',

+ 5 - 1
tests/Feature/ReplyToEmailTest.php

@@ -20,10 +20,14 @@ class ReplyToEmailTest extends TestCase
     {
         parent::setUp();
 
-        $this->user = User::factory()->create(['username' => 'johndoe']);
+        $this->user = User::factory()->create();
         $this->user->recipients()->save($this->user->defaultRecipient);
         $this->user->defaultRecipient->email = 'will@anonaddy.com';
         $this->user->defaultRecipient->save();
+
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */

+ 5 - 1
tests/Feature/SendFromEmailTest.php

@@ -19,10 +19,14 @@ class SendFromEmailTest extends TestCase
     {
         parent::setUp();
 
-        $this->user = User::factory()->create(['username' => 'johndoe']);
+        $this->user = User::factory()->create();
         $this->user->recipients()->save($this->user->defaultRecipient);
         $this->user->defaultRecipient->email = 'will@anonaddy.com';
         $this->user->defaultRecipient->save();
+
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */

+ 20 - 42
tests/Feature/SettingsTest.php

@@ -3,13 +3,13 @@
 namespace Tests\Feature;
 
 use App\Exports\AliasesExport;
-use App\Models\AdditionalUsername;
 use App\Models\Alias;
 use App\Models\AliasRecipient;
 use App\Models\DeletedUsername;
 use App\Models\Domain;
 use App\Models\Recipient;
 use App\Models\User;
+use App\Models\Username;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Str;
@@ -29,6 +29,9 @@ class SettingsTest extends TestCase
         $this->user = User::factory()->create();
         $this->actingAs($this->user);
         $this->user->recipients()->save($this->user->defaultRecipient);
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */
@@ -89,7 +92,7 @@ class SettingsTest extends TestCase
     public function user_cannot_update_default_alias_domain_if_invalid()
     {
         $response = $this->post('/settings/default-alias-domain', [
-            'domain' => 'johndoe.anonaddy.me'
+            'domain' => 'invalid.anonaddy.me'
         ]);
 
         $response->assertStatus(302);
@@ -211,34 +214,6 @@ class SettingsTest extends TestCase
         $this->assertEquals('top', $this->user->banner_location);
     }
 
-    /** @test */
-    public function user_can_enable_catch_all_for_account()
-    {
-        $this->user->update(['catch_all' => false]);
-
-        $this->assertFalse($this->user->catch_all);
-
-        $response = $this->post('/settings/catch-all/', [
-            'catch_all' => true
-        ]);
-
-        $response->assertStatus(302);
-        $this->assertTrue($this->user->catch_all);
-    }
-
-    /** @test */
-    public function user_can_disable_catch_all_for_account()
-    {
-        $this->assertTrue($this->user->catch_all);
-
-        $response = $this->post('/settings/catch-all/', [
-            'catch_all' => false
-        ]);
-
-        $response->assertStatus(302);
-        $this->assertFalse($this->user->catch_all);
-    }
-
     /** @test */
     public function user_can_enable_use_reply_to()
     {
@@ -332,14 +307,14 @@ class SettingsTest extends TestCase
             'aliasable_type' => 'App\Models\Domain'
         ]);
 
-        $additionalUsername = AdditionalUsername::factory()->create([
+        $username = Username::factory()->create([
             'user_id' => $this->user->id
         ]);
 
-        $aliasWithAdditionalUsername = Alias::factory()->create([
+        $aliasWithUsername = Alias::factory()->create([
             'user_id' => $this->user->id,
-            'aliasable_id' => $additionalUsername->id,
-            'aliasable_type' => 'App\Models\AdditionaUsername'
+            'aliasable_id' => $username->id,
+            'aliasable_type' => 'App\Models\Username'
         ]);
 
         $response = $this->post('/settings/account', [
@@ -383,10 +358,10 @@ class SettingsTest extends TestCase
         ]);
 
         $this->assertDatabaseMissing('aliases', [
-            'id' => $aliasWithAdditionalUsername->id,
+            'id' => $aliasWithUsername->id,
             'user_id' => $this->user->id,
-            'aliasable_id' => $additionalUsername->id,
-            'aliasable_type' => 'App\Models\AdditionalUsername'
+            'aliasable_id' => $username->id,
+            'aliasable_type' => 'App\Models\Username'
         ]);
 
         $this->assertDatabaseMissing('recipients', [
@@ -399,13 +374,13 @@ class SettingsTest extends TestCase
             'user_id' => $this->user->id,
         ]);
 
-        $this->assertDatabaseMissing('additional_usernames', [
-            'id' => $additionalUsername->id,
+        $this->assertDatabaseMissing('usernames', [
+            'id' => $username->id,
             'user_id' => $this->user->id
         ]);
 
         $this->assertEquals(DeletedUsername::first()->username, $this->user->username);
-        $this->assertEquals(DeletedUsername::skip(1)->first()->username, $additionalUsername->username);
+        $this->assertEquals(DeletedUsername::skip(1)->first()->username, $username->username);
     }
 
     /** @test */
@@ -429,8 +404,11 @@ class SettingsTest extends TestCase
         $this->assertNull(DeletedUsername::first());
 
         $this->assertDatabaseHas('users', [
-            'id' => $this->user->id,
-            'username' => $this->user->username,
+            'id' => $this->user->id
+        ]);
+
+        $this->assertDatabaseHas('usernames', [
+            'username' => $this->user->username
         ]);
     }
 

+ 4 - 1
tests/Feature/ShowFailedDeliveriesTest.php

@@ -18,7 +18,10 @@ class ShowFailedDeliveriesTest extends TestCase
     {
         parent::setUp();
 
-        $this->user = User::factory()->create(['username' => 'johndoe']);
+        $this->user = User::factory()->create();
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
         $this->actingAs($this->user);
     }
 

+ 6 - 6
tests/Feature/ShowAdditionalUsernamesTest.php → tests/Feature/ShowUsernamesTest.php

@@ -2,13 +2,13 @@
 
 namespace Tests\Feature;
 
-use App\Models\AdditionalUsername;
 use App\Models\User;
+use App\Models\Username;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Support\Carbon;
 use Tests\TestCase;
 
-class ShowAdditionalUsernamesTest extends TestCase
+class ShowUsernamesTest extends TestCase
 {
     use RefreshDatabase;
 
@@ -25,7 +25,7 @@ class ShowAdditionalUsernamesTest extends TestCase
     /** @test */
     public function user_can_view_usernames_from_the_usernames_page()
     {
-        $usernames = AdditionalUsername::factory()->count(3)->create([
+        $usernames = Username::factory()->count(3)->create([
             'user_id' => $this->user->id
         ]);
 
@@ -39,15 +39,15 @@ class ShowAdditionalUsernamesTest extends TestCase
     /** @test */
     public function latest_usernames_are_listed_first()
     {
-        $a = AdditionalUsername::factory()->create([
+        $a = Username::factory()->create([
             'user_id' => $this->user->id,
             'created_at' => Carbon::now()->subDays(15)
         ]);
-        $b = AdditionalUsername::factory()->create([
+        $b = Username::factory()->create([
             'user_id' => $this->user->id,
             'created_at' => Carbon::now()->subDays(5)
         ]);
-        $c = AdditionalUsername::factory()->create([
+        $c = Username::factory()->create([
             'user_id' => $this->user->id,
             'created_at' => Carbon::now()->subDays(10)
         ]);

+ 3 - 0
tests/Unit/AliasTest.php

@@ -21,6 +21,9 @@ class AliasTest extends TestCase
 
         $this->user = User::factory()->create();
         $this->user->recipients()->save($this->user->defaultRecipient);
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */

+ 3 - 0
tests/Unit/UserTest.php

@@ -21,6 +21,9 @@ class UserTest extends TestCase
 
         $this->user = User::factory()->create();
         $this->user->recipients()->save($this->user->defaultRecipient);
+        $this->user->usernames()->save($this->user->defaultUsername);
+        $this->user->defaultUsername->username = 'johndoe';
+        $this->user->defaultUsername->save();
     }
 
     /** @test */

+ 0 - 0
tests/emails/email_additional_username.eml → tests/emails/email_username.eml


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio