Selaa lähdekoodia

Added ability to request username reminder email

Will 4 vuotta sitten
vanhempi
commit
3591a3eb47

+ 4 - 0
README.md

@@ -186,6 +186,10 @@ A few days before your billing cycle ends you will receive an email letting you
 
 
 You will not be able to activate any of the above again until you resubscribe.
 You will not be able to activate any of the above again until you resubscribe.
 
 
+#### **If I subscribe will Stripe see my real email address?**
+
+No, Stripe will instead be given an alias. This alias will only be created if Stripe sends an email to it, for example if your card payment fails or if your card has expired.
+
 #### **How do you prevent spammers?**
 #### **How do you prevent spammers?**
 
 
 The following is in place to help prevent spam:
 The following is in place to help prevent spam:

+ 61 - 0
app/Http/Controllers/Auth/ForgotUsernameController.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Http\Controllers\Controller;
+use App\Recipient;
+use Illuminate\Http\Request;
+
+class ForgotUsernameController extends Controller
+{
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+        $this->middleware('throttle:3,1')->only('sendReminderEmail');
+    }
+
+    /**
+     * Display the form to request a password reset link.
+     *
+     * @return \Illuminate\View\View
+     */
+    public function show()
+    {
+        return view('auth.usernames.email');
+    }
+
+    /**
+     * Send a reset link to the given user.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
+     */
+    public function sendReminderEmail(Request $request)
+    {
+        $this->validateEmail($request);
+
+        $recipient = Recipient::all()->where('email', $request->email)->first();
+
+        if (isset($recipient->user)) {
+            $recipient->user->sendUsernameReminderNotification();
+        }
+
+        return back()->with('status', 'A reminder has been sent if that email exists.');
+    }
+
+    /**
+     * Validate the email for the given request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return void
+     */
+    protected function validateEmail(Request $request)
+    {
+        $request->validate(['email' => 'required|email:rfc,dns']);
+    }
+}

+ 66 - 0
app/Notifications/UsernameReminder.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace App\Notifications;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Notifications\Notification;
+
+class UsernameReminder extends Notification implements ShouldQueue
+{
+    use Queueable;
+
+    /**
+     * Create a new notification instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        //
+    }
+
+    /**
+     * Get the notification's delivery channels.
+     *
+     * @param  mixed  $notifiable
+     * @return array
+     */
+    public function via($notifiable)
+    {
+        return ['mail'];
+    }
+
+    /**
+     * Get the mail representation of the notification.
+     *
+     * @param  mixed  $notifiable
+     * @return \Illuminate\Notifications\Messages\MailMessage
+     */
+    public function toMail($notifiable)
+    {
+        return (new MailMessage)
+            ->subject("AnonAddy Username Reminder")
+            ->markdown('mail.username_reminder', [
+                'username' => $notifiable->username
+            ])
+            ->withSwiftMessage(function ($message) {
+                $message->getHeaders()
+                        ->addTextHeader('Feedback-ID', 'UR:anonaddy');
+            });
+    }
+
+    /**
+     * Get the array representation of the notification.
+     *
+     * @param  mixed  $notifiable
+     * @return array
+     */
+    public function toArray($notifiable)
+    {
+        return [
+            //
+        ];
+    }
+}

+ 11 - 0
app/User.php

@@ -2,6 +2,7 @@
 
 
 namespace App;
 namespace App;
 
 
+use App\Notifications\UsernameReminder;
 use App\Traits\HasEncryptedAttributes;
 use App\Traits\HasEncryptedAttributes;
 use App\Traits\HasUuid;
 use App\Traits\HasUuid;
 use Illuminate\Contracts\Auth\MustVerifyEmail;
 use Illuminate\Contracts\Auth\MustVerifyEmail;
@@ -217,6 +218,16 @@ class User extends Authenticatable implements MustVerifyEmail
         return $this->aliases()->whereDoesntHave('recipients');
         return $this->aliases()->whereDoesntHave('recipients');
     }
     }
 
 
+    /**
+     * Send the username reminder notification.
+     *
+     * @return void
+     */
+    public function sendUsernameReminderNotification()
+    {
+        $this->notify(new UsernameReminder);
+    }
+
     public function hasVerifiedDefaultRecipient()
     public function hasVerifiedDefaultRecipient()
     {
     {
         return ! is_null($this->defaultRecipient->email_verified_at);
         return ! is_null($this->defaultRecipient->email_verified_at);

+ 36 - 40
composer.lock

@@ -396,16 +396,16 @@
         },
         },
         {
         {
             "name": "doctrine/dbal",
             "name": "doctrine/dbal",
-            "version": "2.10.3",
+            "version": "2.10.4",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/doctrine/dbal.git",
                 "url": "https://github.com/doctrine/dbal.git",
-                "reference": "03ca23afc2ee062f5d3e32426ad37c34a4770dcf"
+                "reference": "47433196b6390d14409a33885ee42b6208160643"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/dbal/zipball/03ca23afc2ee062f5d3e32426ad37c34a4770dcf",
-                "reference": "03ca23afc2ee062f5d3e32426ad37c34a4770dcf",
+                "url": "https://api.github.com/repos/doctrine/dbal/zipball/47433196b6390d14409a33885ee42b6208160643",
+                "reference": "47433196b6390d14409a33885ee42b6208160643",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -501,7 +501,7 @@
                     "type": "tidelift"
                     "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2020-09-02T01:35:42+00:00"
+            "time": "2020-09-12T21:20:41+00:00"
         },
         },
         {
         {
             "name": "doctrine/event-manager",
             "name": "doctrine/event-manager",
@@ -1415,16 +1415,16 @@
         },
         },
         {
         {
             "name": "laminas/laminas-zendframework-bridge",
             "name": "laminas/laminas-zendframework-bridge",
-            "version": "1.1.0",
+            "version": "1.1.1",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/laminas/laminas-zendframework-bridge.git",
                 "url": "https://github.com/laminas/laminas-zendframework-bridge.git",
-                "reference": "4939c81f63a8a4968c108c440275c94955753b19"
+                "reference": "6ede70583e101030bcace4dcddd648f760ddf642"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/4939c81f63a8a4968c108c440275c94955753b19",
-                "reference": "4939c81f63a8a4968c108c440275c94955753b19",
+                "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/6ede70583e101030bcace4dcddd648f760ddf642",
+                "reference": "6ede70583e101030bcace4dcddd648f760ddf642",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1436,10 +1436,6 @@
             },
             },
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev",
-                    "dev-develop": "1.1.x-dev"
-                },
                 "laminas": {
                 "laminas": {
                     "module": "Laminas\\ZendFrameworkBridge"
                     "module": "Laminas\\ZendFrameworkBridge"
                 }
                 }
@@ -1469,20 +1465,20 @@
                     "type": "community_bridge"
                     "type": "community_bridge"
                 }
                 }
             ],
             ],
-            "time": "2020-08-18T16:34:51+00:00"
+            "time": "2020-09-14T14:23:00+00:00"
         },
         },
         {
         {
             "name": "laravel/framework",
             "name": "laravel/framework",
-            "version": "v7.28.1",
+            "version": "v7.28.2",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/laravel/framework.git",
                 "url": "https://github.com/laravel/framework.git",
-                "reference": "f7493ab717ca2a9598b1db2d6a3bae8ac8c755e8"
+                "reference": "0956b0688d96565044074b77f521a653d9fce5fb"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/framework/zipball/f7493ab717ca2a9598b1db2d6a3bae8ac8c755e8",
-                "reference": "f7493ab717ca2a9598b1db2d6a3bae8ac8c755e8",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/0956b0688d96565044074b77f521a653d9fce5fb",
+                "reference": "0956b0688d96565044074b77f521a653d9fce5fb",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1627,7 +1623,7 @@
                 "framework",
                 "framework",
                 "laravel"
                 "laravel"
             ],
             ],
-            "time": "2020-09-09T15:02:46+00:00"
+            "time": "2020-09-15T14:48:02+00:00"
         },
         },
         {
         {
             "name": "laravel/passport",
             "name": "laravel/passport",
@@ -1888,16 +1884,16 @@
         },
         },
         {
         {
             "name": "league/commonmark",
             "name": "league/commonmark",
-            "version": "1.5.4",
+            "version": "1.5.5",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/thephpleague/commonmark.git",
                 "url": "https://github.com/thephpleague/commonmark.git",
-                "reference": "21819c989e69bab07e933866ad30c7e3f32984ba"
+                "reference": "45832dfed6007b984c0d40addfac48d403dc6432"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/21819c989e69bab07e933866ad30c7e3f32984ba",
-                "reference": "21819c989e69bab07e933866ad30c7e3f32984ba",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/45832dfed6007b984c0d40addfac48d403dc6432",
+                "reference": "45832dfed6007b984c0d40addfac48d403dc6432",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1909,7 +1905,7 @@
             },
             },
             "require-dev": {
             "require-dev": {
                 "cebe/markdown": "~1.0",
                 "cebe/markdown": "~1.0",
-                "commonmark/commonmark.js": "0.29.1",
+                "commonmark/commonmark.js": "0.29.2",
                 "erusev/parsedown": "~1.0",
                 "erusev/parsedown": "~1.0",
                 "ext-json": "*",
                 "ext-json": "*",
                 "github/gfm": "0.29.0",
                 "github/gfm": "0.29.0",
@@ -1979,7 +1975,7 @@
                     "type": "tidelift"
                     "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2020-08-18T01:19:12+00:00"
+            "time": "2020-09-13T14:44:46+00:00"
         },
         },
         {
         {
             "name": "league/event",
             "name": "league/event",
@@ -2769,16 +2765,16 @@
         },
         },
         {
         {
             "name": "nesbot/carbon",
             "name": "nesbot/carbon",
-            "version": "2.39.2",
+            "version": "2.40.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
-                "reference": "326efde1bc09077a26cb77f6e2e32e13f06c27f2"
+                "reference": "6c7646154181013ecd55e80c201b9fd873c6ee5d"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/326efde1bc09077a26cb77f6e2e32e13f06c27f2",
-                "reference": "326efde1bc09077a26cb77f6e2e32e13f06c27f2",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/6c7646154181013ecd55e80c201b9fd873c6ee5d",
+                "reference": "6c7646154181013ecd55e80c201b9fd873c6ee5d",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -2854,7 +2850,7 @@
                     "type": "tidelift"
                     "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2020-09-10T12:16:42+00:00"
+            "time": "2020-09-11T19:00:58+00:00"
         },
         },
         {
         {
             "name": "nikic/php-parser",
             "name": "nikic/php-parser",
@@ -6884,28 +6880,28 @@
     "packages-dev": [
     "packages-dev": [
         {
         {
             "name": "beyondcode/laravel-dump-server",
             "name": "beyondcode/laravel-dump-server",
-            "version": "1.4.0",
+            "version": "1.5.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/beyondcode/laravel-dump-server.git",
                 "url": "https://github.com/beyondcode/laravel-dump-server.git",
-                "reference": "1f1d18a2e43f96fd67c9f0269c53f8c3814867d9"
+                "reference": "1df6bb3bf13a2e818c1abbb7065ea362277be5c0"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/beyondcode/laravel-dump-server/zipball/1f1d18a2e43f96fd67c9f0269c53f8c3814867d9",
-                "reference": "1f1d18a2e43f96fd67c9f0269c53f8c3814867d9",
+                "url": "https://api.github.com/repos/beyondcode/laravel-dump-server/zipball/1df6bb3bf13a2e818c1abbb7065ea362277be5c0",
+                "reference": "1df6bb3bf13a2e818c1abbb7065ea362277be5c0",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
-                "illuminate/console": "5.6.*|5.7.*|5.8.*|^6.0|^7.0",
-                "illuminate/http": "5.6.*|5.7.*|5.8.*|^6.0|^7.0",
-                "illuminate/support": "5.6.*|5.7.*|5.8.*|^6.0|^7.0",
-                "php": "^7.1",
+                "illuminate/console": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0",
+                "illuminate/http": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0",
+                "illuminate/support": "5.6.*|5.7.*|5.8.*|^6.0|^7.0|^8.0",
+                "php": "^7.2.5",
                 "symfony/var-dumper": "^5.0"
                 "symfony/var-dumper": "^5.0"
             },
             },
             "require-dev": {
             "require-dev": {
                 "larapack/dd": "^1.0",
                 "larapack/dd": "^1.0",
-                "phpunit/phpunit": "^7.0"
+                "phpunit/phpunit": "^7.0|^9.3"
             },
             },
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
@@ -6941,7 +6937,7 @@
                 "beyondcode",
                 "beyondcode",
                 "laravel-dump-server"
                 "laravel-dump-server"
             ],
             ],
-            "time": "2020-03-04T15:23:26+00:00"
+            "time": "2020-09-14T07:14:12+00:00"
         },
         },
         {
         {
             "name": "composer/semver",
             "name": "composer/semver",

+ 13 - 14
package-lock.json

@@ -3160,9 +3160,9 @@
             }
             }
         },
         },
         "escalade": {
         "escalade": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz",
-            "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ=="
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz",
+            "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig=="
         },
         },
         "escape-html": {
         "escape-html": {
             "version": "1.0.3",
             "version": "1.0.3",
@@ -9385,9 +9385,9 @@
             "dev": true
             "dev": true
         },
         },
         "tailwindcss": {
         "tailwindcss": {
-            "version": "1.8.8",
-            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.8.8.tgz",
-            "integrity": "sha512-MCaTFA+ae278rYeB0UTJAkWJMW5eYMMO6/XXBL0oo+SKuZCM4uCFskroHbMFvQSoA96sslFX2+tCPDOv1T7Tbw==",
+            "version": "1.8.10",
+            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.8.10.tgz",
+            "integrity": "sha512-7QkERG/cWCzsuMqHMwjOaLMVixOGLNBiXsrkssxlE1aWfkxVbGqiuMokR2162xRyaH2mBIHKxmlf1qb3DvIPqw==",
             "requires": {
             "requires": {
                 "@fullhuman/postcss-purgecss": "^2.1.2",
                 "@fullhuman/postcss-purgecss": "^2.1.2",
                 "autoprefixer": "^9.4.5",
                 "autoprefixer": "^9.4.5",
@@ -9434,9 +9434,9 @@
                     }
                     }
                 },
                 },
                 "caniuse-lite": {
                 "caniuse-lite": {
-                    "version": "1.0.30001125",
-                    "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001125.tgz",
-                    "integrity": "sha512-9f+r7BW8Qli917mU3j0fUaTweT3f3vnX/Lcs+1C73V+RADmFme+Ih0Br8vONQi3X0lseOe6ZHfsZLCA8MSjxUA=="
+                    "version": "1.0.30001131",
+                    "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001131.tgz",
+                    "integrity": "sha512-4QYi6Mal4MMfQMSqGIRPGbKIbZygeN83QsWq1ixpUwvtfgAZot5BrCKzGygvZaV+CnELdTwD0S4cqUNozq7/Cw=="
                 },
                 },
                 "chalk": {
                 "chalk": {
                     "version": "4.1.0",
                     "version": "4.1.0",
@@ -9466,9 +9466,9 @@
                     "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
                     "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
                 },
                 },
                 "electron-to-chromium": {
                 "electron-to-chromium": {
-                    "version": "1.3.567",
-                    "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.567.tgz",
-                    "integrity": "sha512-1aKkw0Hha1Bw9JA5K5PT5eFXC/TXbkJvUfNSNEciPUMgSIsRJZM1hF2GUEAGZpAbgvd8En21EA+Lv820KOhvqA=="
+                    "version": "1.3.570",
+                    "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz",
+                    "integrity": "sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg=="
                 },
                 },
                 "fs-extra": {
                 "fs-extra": {
                     "version": "8.1.0",
                     "version": "8.1.0",
@@ -10708,8 +10708,7 @@
         },
         },
         "yargs-parser": {
         "yargs-parser": {
             "version": "13.1.1",
             "version": "13.1.1",
-            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
-            "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+            "resolved": "",
             "requires": {
             "requires": {
                 "camelcase": "^5.0.0",
                 "camelcase": "^5.0.0",
                 "decamelize": "^1.2.0"
                 "decamelize": "^1.2.0"

+ 1 - 1
package.json

@@ -21,7 +21,7 @@
         "postcss-import": "^11.1.0",
         "postcss-import": "^11.1.0",
         "postcss-nesting": "^5.0.0",
         "postcss-nesting": "^5.0.0",
         "resolve-url-loader": "^2.3.2",
         "resolve-url-loader": "^2.3.2",
-        "tailwindcss": "^1.8.8",
+        "tailwindcss": "^1.8.10",
         "tippy.js": "^4.3.5",
         "tippy.js": "^4.3.5",
         "v-clipboard": "^2.2.3",
         "v-clipboard": "^2.2.3",
         "vue": "^2.6.12",
         "vue": "^2.6.12",

+ 2 - 7
resources/js/pages/Domains.vue

@@ -275,13 +275,8 @@
           </div>
           </div>
           <div class="table-row">
           <div class="table-row">
             <div class="table-cell py-2">CNAME</div>
             <div class="table-cell py-2">CNAME</div>
-            <div class="table-cell py-2 px-4">dk1._domainkey</div>
-            <div class="table-cell py-2 break-words">dk1._domainkey.{{ domainName }}.</div>
-          </div>
-          <div class="table-row">
-            <div class="table-cell py-2">CNAME</div>
-            <div class="table-cell py-2 px-4">dk2._domainkey</div>
-            <div class="table-cell py-2 break-words">dk2._domainkey.{{ domainName }}.</div>
+            <div class="table-cell py-2 px-4">default._domainkey</div>
+            <div class="table-cell py-2 break-words">default._domainkey.{{ domainName }}.</div>
           </div>
           </div>
           <div class="table-row">
           <div class="table-row">
             <div class="table-cell py-2">TXT</div>
             <div class="table-cell py-2">TXT</div>

+ 1 - 1
resources/views/auth/login.blade.php

@@ -62,7 +62,7 @@
                             </div>
                             </div>
                             @if (Route::has('password.request'))
                             @if (Route::has('password.request'))
                                 <a class="whitespace-no-wrap no-underline text-sm" href="{{ route('password.request') }}">
                                 <a class="whitespace-no-wrap no-underline text-sm" href="{{ route('password.request') }}">
-                                    {{ __('Forgot Password?') }}
+                                    {{ __('Forgot Username/Password?') }}
                                 </a>
                                 </a>
                             @endif
                             @endif
                         </div>
                         </div>

+ 5 - 1
resources/views/auth/passwords/email.blade.php

@@ -24,7 +24,7 @@
                             </div>
                             </div>
                         @endif
                         @endif
 
 
-                        <div class="mt-8 flex flex-wrap">
+                        <div class="mt-8 flex flex-wrap mb-6">
                             <label for="username" class="block text-grey-700 text-sm mb-2">
                             <label for="username" class="block text-grey-700 text-sm mb-2">
                                 {{ __('Username') }}:
                                 {{ __('Username') }}:
                             </label>
                             </label>
@@ -38,6 +38,10 @@
                             @endif
                             @endif
                         </div>
                         </div>
 
 
+                        <a class="whitespace-no-wrap no-underline text-sm" href="{{ route('username.reminder.show') }}">
+                            {{ __('Forgot Username?') }}
+                        </a>
+
                     </div>
                     </div>
 
 
                     <div class="px-6 md:px-10 py-4 bg-grey-50 border-t border-grey-100 flex flex-wrap items-center justify-center">
                     <div class="px-6 md:px-10 py-4 bg-grey-50 border-t border-grey-100 flex flex-wrap items-center justify-center">

+ 63 - 0
resources/views/auth/usernames/email.blade.php

@@ -0,0 +1,63 @@
+@extends('layouts.app')
+
+@section('content')
+    <div class="p-6 bg-indigo-900 min-h-screen flex justify-center items-center">
+        <div class="w-full max-w-md">
+            <div class="flex justify-center text-white mb-6 text-5xl font-bold">
+                <img class="w-48" alt="AnonAddy Logo" src="/svg/logo.svg">
+            </div>
+            <div class="flex flex-col break-words bg-white border border-2 rounded-lg shadow-lg overflow-hidden">
+                <form method="POST" action="{{ route('username.email') }}">
+                    @csrf
+
+                    <div class="px-6 py-8 md:p-10">
+
+                        <h1 class="text-center font-bold text-3xl">
+                            {{ __('Username Reminder') }}
+                        </h1>
+
+                        <div class="mx-auto mt-6 w-24 border-b-2 border-grey-200"></div>
+
+                        @if (session('status'))
+                            <div class="text-sm border-t-8 rounded text-green-700 border-green-600 bg-green-100 px-3 py-4 mt-4" role="alert">
+                                {{ session('status') }}
+                            </div>
+                        @endif
+
+                        <div class="mt-8 flex flex-wrap mb-6">
+                            <label for="email" class="block text-grey-700 text-sm mb-2">
+                                {{ __('Email') }}:
+                            </label>
+
+                            <input id="email" type="text" class="appearance-none bg-grey-100 rounded w-full p-3 text-grey-700 focus:shadow-outline{{ $errors->has('email') ? ' border-red-500' : '' }}" name="email" value="{{ old('email') }}" placeholder="johndoe@example.com" required>
+
+                            @if ($errors->has('email'))
+                                <p class="text-red-500 text-xs italic mt-4">
+                                    {{ $errors->first('email') }}
+                                </p>
+                            @endif
+                        </div>
+
+                        @if (Route::has('password.request'))
+                            <a class="whitespace-no-wrap no-underline text-sm" href="{{ route('password.request') }}">
+                                {{ __('Forgot Password?') }}
+                            </a>
+                        @endif
+
+                    </div>
+
+                    <div class="px-6 md:px-10 py-4 bg-grey-50 border-t border-grey-100 flex flex-wrap items-center justify-center">
+                        <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">
+                            {{ __('Send Username Reminder') }}
+                        </button>
+                    </div>
+                </form>
+            </div>
+                <p class="w-full text-xs text-center mt-6">
+                    <a class="text-white hover:text-indigo-50 no-underline" href="{{ route('login') }}">
+                        Back to login
+                    </a>
+                </p>
+        </div>
+    </div>
+@endsection

+ 12 - 0
resources/views/mail/username_reminder.blade.php

@@ -0,0 +1,12 @@
+@component('mail::message')
+
+# Username Reminder
+
+The account associated with this email address has the following username: **{{ $username }}**
+
+If you've also forgotten your password you can use this username to reset it.
+
+@component('mail::button', ['url' => config('app.url').'/login'])
+Login Now
+@endcomponent
+@endcomponent

+ 2 - 0
routes/web.php

@@ -12,6 +12,8 @@
 */
 */
 
 
 Auth::routes(['verify' => true, 'register' => config('anonaddy.enable_registration')]);
 Auth::routes(['verify' => true, 'register' => config('anonaddy.enable_registration')]);
+Route::get('/username/reminder', 'Auth\ForgotUsernameController@show')->name('username.reminder.show');
+Route::post('/username/email', 'Auth\ForgotUsernameController@sendReminderEmail')->name('username.email');
 
 
 Route::post('/login/2fa', 'Auth\TwoFactorAuthController@authenticateTwoFactor')->name('login.2fa')->middleware(['2fa', 'throttle', 'auth']);
 Route::post('/login/2fa', 'Auth\TwoFactorAuthController@authenticateTwoFactor')->name('login.2fa')->middleware(['2fa', 'throttle', 'auth']);
 
 

+ 29 - 0
tests/Feature/LoginTest.php

@@ -2,9 +2,11 @@
 
 
 namespace Tests\Feature;
 namespace Tests\Feature;
 
 
+use App\Notifications\UsernameReminder;
 use App\User;
 use App\User;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\Notification;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
 use Tests\TestCase;
 use Tests\TestCase;
 
 
@@ -72,4 +74,31 @@ class LoginTest extends TestCase
             ->assertRedirect('/recipients')
             ->assertRedirect('/recipients')
             ->assertSessionHasNoErrors();
             ->assertSessionHasNoErrors();
     }
     }
+
+    /** @test */
+    public function user_can_receive_username_reminder_email()
+    {
+        Notification::fake();
+
+        $this->post('/username/email', [
+            'email' => $this->user->email
+        ]);
+
+        Notification::assertSentTo(
+            $this->user,
+            UsernameReminder::class
+        );
+    }
+
+    /** @test */
+    public function username_reminder_email_not_sent_for_unkown_email()
+    {
+        Notification::fake();
+
+        $this->post('/username/email', [
+            'email' => 'doesnotexist@example.com'
+        ]);
+
+        Notification::assertNothingSent();
+    }
 }
 }

+ 0 - 2
tests/Feature/RegistrationTest.php

@@ -50,8 +50,6 @@ class RegistrationTest extends TestCase
     /** @test */
     /** @test */
     public function user_cannot_register_with_invalid_characters()
     public function user_cannot_register_with_invalid_characters()
     {
     {
-        Notification::fake();
-
         $response = $this->post('/register', [
         $response = $this->post('/register', [
             'username' => 'Ω',
             'username' => 'Ω',
             'email' => 'johndoe@example.com',
             'email' => 'johndoe@example.com',