Browse Source

Merge pull request #3829 from pixelfed/staging

In-App Registration
daniel 2 years ago
parent
commit
e7a8dec1a9

+ 3 - 2
CHANGELOG.md

@@ -3,9 +3,11 @@
 ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.4...dev)
 
 ### New Features
+- Mobile App Registration ([#3829](https://github.com/pixelfed/pixelfed/pull/3829))
 - Portfolios ([#3705](https://github.com/pixelfed/pixelfed/pull/3705))
 - Server Directory ([#3762](https://github.com/pixelfed/pixelfed/pull/3762))
 - Manually verify email address (php artisan user:verifyemail) ([682f5f0f](https://github.com/pixelfed/pixelfed/commit/682f5f0f))
+- Manually generate in-app registration confirmation links (php artisan user:app-magic-link) ([73eb9e36](https://github.com/pixelfed/pixelfed/commit/73eb9e36))
 
 ### Updates
 - Update ApiV1Controller, include self likes in favourited_by endpoint ([58b331d2](https://github.com/pixelfed/pixelfed/commit/58b331d2))
@@ -26,8 +28,7 @@
 - Update landing view, add `app.name` and `app.short_description` for better customizability ([bda9d16b](https://github.com/pixelfed/pixelfed/commit/bda9d16b))
 - Update Profile, fix avatarUrl paths. Fixes #3559 #3634 ([989e4249](https://github.com/pixelfed/pixelfed/commit/989e4249))
 - Update InboxPipeline, bump request timeout from 5s to 60s ([bb120019](https://github.com/pixelfed/pixelfed/commit/bb120019))
-- Update web routes, fix missing hom route ([a9f4ddfc](https://github.com/pixelfed/pixelfed/commit/a9f4ddfc))
--  ([](https://github.com/pixelfed/pixelfed/commit/))
+- Update web routes, fix missing home route ([a9f4ddfc](https://github.com/pixelfed/pixelfed/commit/a9f4ddfc))
 
 ## [v0.11.4 (2022-10-04)](https://github.com/pixelfed/pixelfed/compare/v0.11.3...v0.11.4)
 

+ 123 - 0
app/Console/Commands/AvatarStorage.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Avatar;
+use App\User;
+use Storage;
+use App\Util\Lexer\PrettyNumber;
+
+class AvatarStorage extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'avatar:storage';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Manage avatar storage';
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->info('Pixelfed Avatar Storage Manager');
+        $this->line(' ');
+        $segments = [
+            [
+                'Local',
+                Avatar::whereNull('is_remote')->count(),
+                PrettyNumber::size(Avatar::whereNull('is_remote')->sum('size'))
+            ],
+            [
+                'Remote',
+                Avatar::whereNotNull('is_remote')->count(),
+                PrettyNumber::size(Avatar::whereNotNull('is_remote')->sum('size'))
+            ],
+            [
+                'Cached (CDN)',
+                Avatar::whereNotNull('cdn_url')->count(),
+                PrettyNumber::size(Avatar::whereNotNull('cdn_url')->sum('size'))
+            ],
+            [
+                'Uncached',
+                Avatar::whereNull('is_remote')->whereNull('cdn_url')->count(),
+                PrettyNumber::size(Avatar::whereNull('is_remote')->whereNull('cdn_url')->sum('size'))
+            ],
+            [
+                '------------',
+                '----------',
+                '-----'
+            ],
+            [
+                'Total',
+                Avatar::count(),
+                PrettyNumber::size(Avatar::sum('size'))
+            ],
+        ];
+        $this->table(
+            ['Segment', 'Count', 'Space Used'],
+            $segments
+        );
+
+        $this->line(' ');
+
+        if(config_cache('pixelfed.cloud_storage')) {
+            $this->info('✅ - Cloud storage configured');
+            $this->line(' ');
+        }
+
+        if(config_cache('instance.avatar.local_to_cloud')) {
+            $this->info('✅ - Store avatars on cloud filesystem');
+            $this->line(' ');
+        }
+
+        if(config_cache('pixelfed.cloud_storage') && config_cache('instance.avatar.local_to_cloud')) {
+            $disk = Storage::disk(config_cache('filesystems.cloud'));
+            $exists = $disk->exists('cache/avatars/default.jpg');
+            $state = $exists ? '✅' : '❌';
+            $msg = $state . ' - Cloud default avatar exists';
+            $this->info($msg);
+        }
+
+        $choice = $this->choice(
+            'Select action:',
+            [
+                'Upload default avatar to cloud',
+                'Move local avatars to cloud',
+                'Move cloud avatars to local'
+            ],
+            0
+        );
+
+        return $this->handleChoice($choice);
+    }
+
+    protected function handleChoice($id)
+    {
+        switch ($id) {
+            case 'Upload default avatar to cloud':
+                return $this->uploadDefaultAvatar();
+                break;
+        }
+    }
+
+    protected function uploadDefaultAvatar()
+    {
+        $disk = Storage::disk(config_cache('filesystems.cloud'));
+        $disk->put('cache/avatars/default.jpg', Storage::get('public/avatars/default.jpg'));
+        Avatar::whereMediaPath('public/avatars/default.jpg')->update(['cdn_url' => $disk->url('cache/avatars/default.jpg')]);
+        $this->info('Successfully uploaded default avatar to cloud storage!');
+        $this->info($disk->url('cache/avatars/default.jpg'));
+    }
+}

+ 82 - 0
app/Console/Commands/UserRegistrationMagicLink.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\EmailVerification;
+use App\User;
+
+class UserRegistrationMagicLink extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'user:app-magic-link {--username=} {--email=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Get the app magic link for users who register in-app but have not recieved the confirmation email';
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $username = $this->option('username');
+        $email = $this->option('email');
+        if(!$username && !$email) {
+            $this->error('Please provide the username or email as arguments');
+            $this->line(' ');
+            $this->info('Example: ');
+            $this->info('php artisan user:app-magic-link --username=dansup');
+            $this->info('php artisan user:app-magic-link --email=dansup@pixelfed.com');
+            return;
+        }
+        $user = User::when($username, function($q, $username) {
+            return $q->whereUsername($username);
+        })
+        ->when($email, function($q, $email) {
+            return $q->whereEmail($email);
+        })
+        ->first();
+
+        if(!$user) {
+            $this->error('We cannot find any matching accounts');
+            return;
+        }
+
+        if($user->email_verified_at) {
+            $this->error('User already verified email address');
+            return;
+        }
+
+        if(!$user->register_source || $user->register_source !== 'app' || !$user->app_register_token) {
+            $this->error('User did not register via app');
+            return;
+        }
+
+        $verify = EmailVerification::whereUserId($user->id)->first();
+
+        if(!$verify) {
+            $this->error('Cannot find user verification codes');
+            return;
+        }
+
+        $appUrl = 'pixelfed://confirm-account/'. $user->app_register_token . '?rt=' . $verify->random_token;
+        $this->line(' ');
+        $this->info('Magic link found! Copy the following link and send to user');
+        $this->line(' ');
+        $this->line(' ');
+        $this->info($appUrl);
+        $this->line(' ');
+        $this->line(' ');
+        return Command::SUCCESS;
+    }
+}

+ 148 - 0
app/Http/Controllers/Api/ApiV1Dot1Controller.php

@@ -14,12 +14,18 @@ use App\EmailVerification;
 use App\Status;
 use App\Report;
 use App\Profile;
+use App\User;
 use App\Services\AccountService;
 use App\Services\StatusService;
 use App\Services\ProfileStatusService;
+use App\Util\Lexer\RestrictedNames;
+use App\Services\EmailService;
+use Illuminate\Support\Str;
+use Illuminate\Support\Facades\Hash;
 use Jenssegers\Agent\Agent;
 use Mail;
 use App\Mail\PasswordChange;
+use App\Mail\ConfirmAppEmail;
 
 class ApiV1Dot1Controller extends Controller
 {
@@ -402,4 +408,146 @@ class ApiV1Dot1Controller extends Controller
 
         return $this->json($res);
     }
+
+    public function inAppRegistrationPreFlightCheck(Request $request)
+    {
+        return [
+            'open' => config('pixelfed.open_registration'),
+            'iara' => config('pixelfed.allow_app_registration')
+        ];
+    }
+
+    public function inAppRegistration(Request $request)
+    {
+        abort_if($request->user(), 404);
+        abort_unless(config('pixelfed.open_registration'), 404);
+        abort_unless(config('pixelfed.allow_app_registration'), 404);
+        abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
+        $this->validate($request, [
+            'email' => [
+                'required',
+                'string',
+                'email',
+                'max:255',
+                'unique:users',
+                function ($attribute, $value, $fail) {
+                    $banned = EmailService::isBanned($value);
+                    if($banned) {
+                        return $fail('Email is invalid.');
+                    }
+                },
+            ],
+            'username' => [
+                'required',
+                'min:2',
+                'max:15',
+                'unique:users',
+                function ($attribute, $value, $fail) {
+                    $dash = substr_count($value, '-');
+                    $underscore = substr_count($value, '_');
+                    $period = substr_count($value, '.');
+
+                    if(ends_with($value, ['.php', '.js', '.css'])) {
+                        return $fail('Username is invalid.');
+                    }
+
+                    if(($dash + $underscore + $period) > 1) {
+                        return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
+                    }
+
+                    if (!ctype_alnum($value[0])) {
+                        return $fail('Username is invalid. Must start with a letter or number.');
+                    }
+
+                    if (!ctype_alnum($value[strlen($value) - 1])) {
+                        return $fail('Username is invalid. Must end with a letter or number.');
+                    }
+
+                    $val = str_replace(['_', '.', '-'], '', $value);
+                    if(!ctype_alnum($val)) {
+                        return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
+                    }
+
+                    $restricted = RestrictedNames::get();
+                    if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
+                        return $fail('Username cannot be used.');
+                    }
+                },
+            ],
+            'password' => 'required|string|min:8',
+            // 'avatar' => 'required|mimetypes:image/jpeg,image/png|max:15000',
+            // 'bio' => 'required|max:140'
+        ]);
+
+        $email = $request->input('email');
+        $username = $request->input('username');
+        $password = $request->input('password');
+
+        if(config('database.default') == 'pgsql') {
+            $username = strtolower($username);
+            $email = strtolower($email);
+        }
+
+        $user = new User;
+        $user->name = $username;
+        $user->username = $username;
+        $user->email = $email;
+        $user->password = Hash::make($password);
+        $user->register_source = 'app';
+        $user->app_register_ip = $request->ip();
+        $user->app_register_token = Str::random(32);
+        $user->save();
+
+        $rtoken = Str::random(mt_rand(64, 70));
+
+        $verify = new EmailVerification();
+        $verify->user_id = $user->id;
+        $verify->email = $user->email;
+        $verify->user_token = $user->app_register_token;
+        $verify->random_token = $rtoken;
+        $verify->save();
+
+        $appUrl = 'pixelfed://confirm-account/'. $user->app_register_token . '?rt=' . $rtoken;
+
+        Mail::to($user->email)->send(new ConfirmAppEmail($verify, $appUrl));
+
+        return response()->json([
+            'success' => true,
+        ]);
+    }
+
+    public function inAppRegistrationConfirm(Request $request)
+    {
+        abort_if($request->user(), 404);
+        abort_unless(config('pixelfed.open_registration'), 404);
+        abort_unless(config('pixelfed.allow_app_registration'), 404);
+        abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
+        $this->validate($request, [
+            'user_token' => 'required',
+            'random_token' => 'required',
+            'email' => 'required'
+        ]);
+
+        $verify = EmailVerification::whereEmail($request->input('email'))
+            ->whereUserToken($request->input('user_token'))
+            ->whereRandomToken($request->input('random_token'))
+            ->first();
+
+        if(!$verify) {
+            return response()->json(['error' => 'Invalid tokens'], 403);
+        }
+
+        $user = User::findOrFail($verify->user_id);
+        $user->email_verified_at = now();
+        $user->last_active_at = now();
+        $user->save();
+
+        $verify->delete();
+
+        $token = $user->createToken('Pixelfed');
+
+        return response()->json([
+            'access_token' => $token->accessToken
+        ]);
+    }
 }

+ 15 - 1
app/Http/Controllers/PublicApiController.php

@@ -307,6 +307,7 @@ class PublicApiController extends Controller
         $user = $request->user();
         $filtered = $user ? UserFilterService::filters($user->profile_id) : [];
 
+        $hideNsfw = config('instance.hide_nsfw_on_public_feeds');
         if(config('exp.cached_public_timeline') == false) {
             if($min || $max) {
                 $dir = $min ? '>' : '<';
@@ -322,6 +323,9 @@ class PublicApiController extends Controller
                           ->whereNull(['in_reply_to_id', 'reblog_of_id'])
                           ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
                           ->whereLocal(true)
+                          ->when($hideNsfw, function($q, $hideNsfw) {
+                            return $q->where('is_nsfw', false);
+                          })
                           ->whereScope('public')
                           ->orderBy('id', 'desc')
                           ->limit($limit)
@@ -365,6 +369,9 @@ class PublicApiController extends Controller
                           ->whereNull(['in_reply_to_id', 'reblog_of_id'])
                           ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
                           ->whereLocal(true)
+                          ->when($hideNsfw, function($q, $hideNsfw) {
+                            return $q->where('is_nsfw', false);
+                          })
                           ->whereScope('public')
                           ->orderBy('id', 'desc')
                           ->limit($limit)
@@ -608,6 +615,7 @@ class PublicApiController extends Controller
         $amin = SnowflakeService::byDate(now()->subDays(config('federation.network_timeline_days_falloff')));
 
         $filtered = $user ? UserFilterService::filters($user->profile_id) : [];
+        $hideNsfw = config('instance.hide_nsfw_on_public_feeds');
 
         if(config('instance.timeline.network.cached') == false) {
 	        if($min || $max) {
@@ -620,7 +628,10 @@ class PublicApiController extends Controller
 	                        'scope',
 	                        'created_at',
 	                      )
-	                      ->where('id', $dir, $id)
+                          ->where('id', $dir, $id)
+                          ->when($hideNsfw, function($q, $hideNsfw) {
+                            return $q->where('is_nsfw', false);
+                          })
 	                      ->whereNull(['in_reply_to_id', 'reblog_of_id'])
 	                      ->whereNotIn('profile_id', $filtered)
 	                      ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
@@ -648,6 +659,9 @@ class PublicApiController extends Controller
 	                          )
 	                      	  ->whereNull(['in_reply_to_id', 'reblog_of_id'])
 	                          ->whereNotIn('profile_id', $filtered)
+                              ->when($hideNsfw, function($q, $hideNsfw) {
+                                return $q->where('is_nsfw', false);
+                              })
 	                          ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
 	                          ->whereNotNull('uri')
 	                          ->whereScope('public')

+ 21 - 0
app/Jobs/AvatarPipeline/AvatarOptimize.php

@@ -13,6 +13,7 @@ use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Support\Str;
 use Image as Intervention;
+use Storage;
 
 class AvatarOptimize implements ShouldQueue
 {
@@ -63,6 +64,13 @@ class AvatarOptimize implements ShouldQueue
 			$avatar->save();
 			Cache::forget('avatar:' . $avatar->profile_id);
 			$this->deleteOldAvatar($avatar->media_path, $this->current);
+
+			if(config_cache('pixelfed.cloud_storage') && config_cache('instance.avatar.local_to_cloud')) {
+				$this->uploadToCloud($avatar);
+			} else {
+				$avatar->cdn_url = null;
+				$avatar->save();
+			}
 		} catch (Exception $e) {
 		}
 	}
@@ -79,4 +87,17 @@ class AvatarOptimize implements ShouldQueue
 			@unlink($current);
 		}
 	}
+
+	protected function uploadToCloud($avatar)
+	{
+		$base = 'cache/avatars/' . $avatar->profile_id;
+		$disk = Storage::disk(config('filesystems.cloud'));
+		$disk->deleteDirectory($base);
+		$path = $base . '/' . 'a' . strtolower(Str::random(random_int(3,6))) . $avatar->change_count . '.' . pathinfo($avatar->media_path, PATHINFO_EXTENSION);
+		$url = $disk->put($path, Storage::get($avatar->media_path));
+		$avatar->cdn_url = $disk->url($path);
+		$avatar->save();
+		Storage::delete($avatar->media_path);
+		Cache::forget('avatar:' . $avatar->profile_id);
+	}
 }

+ 3 - 2
app/Jobs/StatusPipeline/StatusEntityLexer.php

@@ -166,12 +166,13 @@ class StatusEntityLexer implements ShouldQueue
 		if(config_cache('pixelfed.bouncer.enabled')) {
 			Bouncer::get($status);
 		}
-
+		$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
 		if( $status->uri == null &&
 			$status->scope == 'public' &&
 			in_array($status->type, $types) &&
 			$status->in_reply_to_id === null &&
-			$status->reblog_of_id === null
+			$status->reblog_of_id === null &&
+			($hideNsfw ? $status->is_nsfw == false : true)
 		) {
 			PublicTimelineService::add($status->id);
 		}

+ 67 - 0
app/Mail/ConfirmAppEmail.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Mail;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Mail\Mailable;
+use Illuminate\Mail\Mailables\Content;
+use Illuminate\Mail\Mailables\Envelope;
+use Illuminate\Queue\SerializesModels;
+
+class ConfirmAppEmail extends Mailable
+{
+    use Queueable, SerializesModels;
+
+    public $verify;
+    public $appUrl;
+
+    /**
+     * Create a new message instance.
+     *
+     * @return void
+     */
+    public function __construct($verify, $url)
+    {
+        $this->verify = $verify;
+        $this->appUrl = $url;
+    }
+
+    /**
+     * Get the message envelope.
+     *
+     * @return \Illuminate\Mail\Mailables\Envelope
+     */
+    public function envelope()
+    {
+        return new Envelope(
+            subject: 'Complete Account Registration',
+        );
+    }
+
+    /**
+     * Get the message content definition.
+     *
+     * @return \Illuminate\Mail\Mailables\Content
+     */
+    public function content()
+    {
+        return new Content(
+            markdown: 'emails.confirm_app_email',
+            with: [
+                'verify' => $this->verify,
+                'appUrl' => $this->appUrl
+            ],
+        );
+    }
+
+    /**
+     * Get the attachments for the message.
+     *
+     * @return array
+     */
+    public function attachments()
+    {
+        return [];
+    }
+}

+ 4 - 0
app/Services/NetworkTimelineService.php

@@ -75,9 +75,13 @@ class NetworkTimelineService
 	public static function warmCache($force = false, $limit = 100)
 	{
 		if(self::count() == 0 || $force == true) {
+			$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
 			Redis::del(self::CACHE_KEY);
 			$ids = Status::whereNotNull('uri')
 				->whereScope('public')
+				->when($hideNsfw, function($q, $hideNsfw) {
+                  return $q->where('is_nsfw', false);
+                })
 				->whereNull('in_reply_to_id')
 				->whereNull('reblog_of_id')
 				->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])

+ 4 - 0
app/Services/PublicTimelineService.php

@@ -75,9 +75,13 @@ class PublicTimelineService {
 	public static function warmCache($force = false, $limit = 100)
 	{
 		if(self::count() == 0 || $force == true) {
+			$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
 			Redis::del(self::CACHE_KEY);
 			$ids = Status::whereNull('uri')
 				->whereNull('in_reply_to_id')
+				->when($hideNsfw, function($q, $hideNsfw) {
+                  return $q->where('is_nsfw', false);
+                })
 				->whereNull('reblog_of_id')
 				->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
 				->whereScope('public')

+ 6 - 0
config/instance.php

@@ -91,4 +91,10 @@ return [
 		'profile' => env('INSTANCE_PROFILE_EMBEDS', true),
 		'post' => env('INSTANCE_POST_EMBEDS', true),
 	],
+
+	'hide_nsfw_on_public_feeds' => env('PF_HIDE_NSFW_ON_PUBLIC_FEEDS', false),
+
+	'avatar' => [
+		'local_to_cloud' => env('PF_LOCAL_AVATAR_TO_CLOUD', false)
+	],
 ];

+ 2 - 0
config/pixelfed.php

@@ -276,4 +276,6 @@ return [
 	'media_fast_process' => env('PF_MEDIA_FAST_PROCESS', true),
 
 	'max_altext_length' => env('PF_MEDIA_MAX_ALTTEXT_LENGTH', 1000),
+
+	'allow_app_registration' => env('PF_ALLOW_APP_REGISTRATION', true),
 ];

+ 36 - 0
database/migrations/2022_11_24_065214_add_register_source_to_users_table.php

@@ -0,0 +1,36 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->string('register_source')->default('web')->nullable()->index();
+            $table->string('app_register_token')->nullable();
+            $table->string('app_register_ip')->nullable();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('register_source');
+            $table->dropColumn('app_register_token');
+            $table->dropColumn('app_register_ip');
+        });
+    }
+};

File diff suppressed because it is too large
+ 9411 - 25
package-lock.json


+ 5 - 2
package.json

@@ -14,11 +14,14 @@
 		"bootstrap": "^4.5.2",
 		"cross-env": "^5.2.1",
 		"jquery": "^3.6.0",
+		"laravel-echo": "^1.12.0",
+		"laravel-mix-make-file-hash": "^2.2.0",
 		"lodash": "^4.17.21",
 		"popper.js": "^1.16.1",
+		"pusher-js": "^7.1.1-beta",
 		"resolve-url-loader": "^5.0.0",
 		"sass": "^1.52.1",
-		"sass-loader": "^7.3.1",
+		"sass-loader": "^12.3.0",
 		"vue": "^2.6.14",
 		"vue-loader": "^15.9.8",
 		"vue-masonry-css": "^1.0.3",
@@ -36,6 +39,7 @@
 		"bigpicture": "^2.6.2",
 		"blurhash": "^1.1.3",
 		"bootstrap-vue": "^2.22.0",
+		"caniuse-lite": "^1.0.30001418",
 		"chart.js": "^2.7.2",
 		"filesize": "^3.6.1",
 		"hls.js": "^1.1.5",
@@ -44,7 +48,6 @@
 		"jquery-scroll-lock": "^3.1.3",
 		"jquery.scrollbar": "^0.2.11",
 		"js-cookie": "^2.2.0",
-		"laravel-echo": "^1.11.7",
 		"laravel-mix": "^6.0.43",
 		"plyr": "^3.7.2",
 		"promise-polyfill": "8.1.0",

+ 18 - 0
resources/views/emails/confirm_app_email.blade.php

@@ -0,0 +1,18 @@
+<x-mail::message>
+# Complete Account Registration
+
+Hello **{{'@'.$verify->user->username}}**,
+
+You are moments away from finishing your new account registration!
+
+@component('mail::button', ['url' => $appUrl])
+Complete Account Registration
+@endcomponent
+
+<p style="color: #d6d3d1;font-size: 10pt">Make sure you click on the button from your mobile device, opening the link using a desktop browser won't work.</p>
+<br>
+<p>If you did not create this account, please disregard this email.</p>
+
+Thanks,<br>
+<a href="{{ config('app.url') }}">{{ config('pixelfed.domain.app') }}</a>
+</x-mail::message>

+ 6 - 0
routes/api.php

@@ -149,6 +149,12 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
 		Route::group(['prefix' => 'directory'], function () use($middleware) {
 			Route::get('listing', 'PixelfedDirectoryController@get');
 		});
+
+		Route::group(['prefix' => 'auth'], function () use($middleware) {
+			Route::get('iarpfc', 'Api\ApiV1Dot1Controller@inAppRegistrationPreFlightCheck');
+			Route::post('iar', 'Api\ApiV1Dot1Controller@inAppRegistration');
+			Route::post('iarc', 'Api\ApiV1Dot1Controller@inAppRegistrationConfirm');
+		});
 	});
 
 	Route::group(['prefix' => 'live'], function() use($middleware) {

Some files were not shown because too many files changed in this diff