From 1ba17b2bc4d09027a0a84b21318f1cdff1716323 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 16 Dec 2018 22:38:43 -0700 Subject: [PATCH 01/30] Update FederationController --- app/Http/Controllers/FederationController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php index aaba0cc4d..f649f19e7 100644 --- a/app/Http/Controllers/FederationController.php +++ b/app/Http/Controllers/FederationController.php @@ -14,6 +14,7 @@ use Carbon\Carbon; use Illuminate\Http\Request; use League\Fractal; use App\Util\ActivityPub\Helpers; +use App\Util\ActivityPub\HttpSignature; class FederationController extends Controller { @@ -113,7 +114,9 @@ class FederationController extends Controller ]; }); - return response()->json($res, 200, [], JSON_PRETTY_PRINT); + return response()->json($res, 200, [ + 'Access-Control-Allow-Origin' => '*' + ]); } public function webfinger(Request $request) From 100f102396beed2a218ca2f8ffaef392e94f4acc Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 16 Dec 2018 22:44:01 -0700 Subject: [PATCH 02/30] Add HttpSignature handlers --- app/Util/ActivityPub/HttpSignature.php | 121 +++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 app/Util/ActivityPub/HttpSignature.php diff --git a/app/Util/ActivityPub/HttpSignature.php b/app/Util/ActivityPub/HttpSignature.php new file mode 100644 index 000000000..d4e4cbecd --- /dev/null +++ b/app/Util/ActivityPub/HttpSignature.php @@ -0,0 +1,121 @@ +private_key); + openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256); + $signature = base64_encode($signature); + $signatureHeader = 'keyId="'.$user->keyId().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"'; + unset($headers['(request-target)']); + $headers['Signature'] = $signatureHeader; + + return self::_headersToCurlArray($headers); + } + + public static function parseSignatureHeader($signature) { + $parts = explode(',', $signature); + $signatureData = []; + + foreach($parts as $part) { + if(preg_match('/(.+)="(.+)"/', $part, $match)) { + $signatureData[$match[1]] = $match[2]; + } + } + + if(!isset($signatureData['keyId'])) { + return [ + 'error' => 'No keyId was found in the signature header. Found: '.implode(', ', array_keys($signatureData)) + ]; + } + + if(!filter_var($signatureData['keyId'], FILTER_VALIDATE_URL)) { + return [ + 'error' => 'keyId is not a URL: '.$signatureData['keyId'] + ]; + } + + if(!isset($signatureData['headers']) || !isset($signatureData['signature'])) { + return [ + 'error' => 'Signature is missing headers or signature parts' + ]; + } + + return $signatureData; + } + + public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body) { + $digest = 'SHA-256='.base64_encode(hash('sha256', $body, true)); + $headersToSign = []; + foreach(explode(' ',$signatureData['headers']) as $h) { + if($h == '(request-target)') { + $headersToSign[$h] = 'post '.$path; + } elseif($h == 'digest') { + $headersToSign[$h] = $digest; + } elseif(isset($inputHeaders[$h][0])) { + $headersToSign[$h] = $inputHeaders[$h][0]; + } + } + $signingString = self::_headersToSigningString($headersToSign); + + $verified = openssl_verify($signingString, base64_decode($signatureData['signature']), $publicKey, OPENSSL_ALGO_SHA256); + + return [$verified, $signingString]; + } + + private static function _headersToSigningString($headers) { + return implode("\n", array_map(function($k, $v){ + return strtolower($k).': '.$v; + }, array_keys($headers), $headers)); + } + + private static function _headersToCurlArray($headers) { + return array_map(function($k, $v){ + return "$k: $v"; + }, array_keys($headers), $headers); + } + + private static function _digest($body) { + if(is_array($body)) { + $body = json_encode($body); + } + return base64_encode(hash('sha256', $body, true)); + } + + protected static function _headersToSign($url, $digest = false) { + $date = new DateTime('UTC'); + + $headers = [ + '(request-target)' => 'post '.parse_url($url, PHP_URL_PATH), + 'Date' => $date->format('D, d M Y H:i:s \G\M\T'), + 'Host' => parse_url($url, PHP_URL_HOST), + 'Content-Type' => 'application/activity+json', + ]; + + if($digest) { + $headers['Digest'] = 'SHA-256='.$digest; + } + + return $headers; + } + +} From 75c7fcd18228224cbf5e694d2434abd5b5331002 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 17 Dec 2018 22:33:21 -0700 Subject: [PATCH 03/30] Update HttpSignature lib --- app/Util/ActivityPub/HttpSignature.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Util/ActivityPub/HttpSignature.php b/app/Util/ActivityPub/HttpSignature.php index d4e4cbecd..72a874f79 100644 --- a/app/Util/ActivityPub/HttpSignature.php +++ b/app/Util/ActivityPub/HttpSignature.php @@ -4,7 +4,7 @@ namespace App\Util\ActivityPub; use Log; use App\Profile; -use DateTime; +use \DateTime; class HttpSignature { From 0144a5fdf6dd71156208e4ac98d68cdcecfb3c0e Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 17 Dec 2018 23:27:07 -0700 Subject: [PATCH 04/30] Update FederationController, add inbox logic --- app/Http/Controllers/FederationController.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php index f649f19e7..98bd9f52b 100644 --- a/app/Http/Controllers/FederationController.php +++ b/app/Http/Controllers/FederationController.php @@ -170,6 +170,29 @@ XML; public function userInbox(Request $request, $username) { + if (config('pixelfed.activitypub_enabled') == false) { + abort(403); + } + + $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); + $body = $request->getContent(); + $bodyDecoded = json_decode($body, true); + $signature = $request->header('signature'); + if(!$signature) { + abort(400, 'Missing signature header'); + } + $signatureData = HttpSignature::parseSignatureHeader($signature); + $actor = Profile::whereKeyId($signatureData['keyId'])->first(); + if(!$actor) { + $actor = Helpers::profileFirstOrNew($bodyDecoded['actor']); + } + $pkey = openssl_pkey_get_public($actor->public_key); + $inboxPath = "/users/{$profile->username}/inbox"; + list($verified, $headers) = HTTPSignature::verify($pkey, $signatureData, $request->headers->all(), $inboxPath, $body); + if($verified !== 1) { + abort(400, 'Invalid signature.'); + } + InboxWorker::dispatch($request->headers->all(), $profile, $bodyDecoded); return; } From c7a4adb8ad8bdfa7120a7e85728e5022f0aa951c Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 17 Dec 2018 23:27:58 -0700 Subject: [PATCH 05/30] Update User model --- app/User.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/User.php b/app/User.php index 16d1b3078..698b8b7d2 100644 --- a/app/User.php +++ b/app/User.php @@ -54,6 +54,14 @@ class User extends Authenticatable return $this->hasOne(UserSetting::class); } + public function statuses() + { + return $this->hasManyThrough( + Status::class, + Profile::class + ); + } + public function receivesBroadcastNotificationsOn() { return 'App.User.'.$this->id; From 1cba67b781b39d5c9479620366839c4f02767028 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 17 Dec 2018 23:28:46 -0700 Subject: [PATCH 06/30] Update Profile model --- app/Profile.php | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/app/Profile.php b/app/Profile.php index 4176a3101..a66f476ce 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -2,27 +2,16 @@ namespace App; +use Auth, Cache, Storage; use App\Util\Lexer\PrettyNumber; -use Auth; -use Cache; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\SoftDeletes; -use Storage; +use Illuminate\Database\Eloquent\{Model, SoftDeletes}; class Profile extends Model { use SoftDeletes; - /** - * The attributes that should be mutated to dates. - * - * @var array - */ protected $dates = ['deleted_at']; - protected $hidden = [ - 'private_key', - ]; - + protected $hidden = ['private_key']; protected $visible = ['username', 'name']; public function user() @@ -30,26 +19,19 @@ class Profile extends Model return $this->belongsTo(User::class); } - public function url($suffix = '') + public function url($suffix = null) { - if ($this->remote_url) { - return $this->remote_url; - } else { - return url($this->username.$suffix); - } + return $this->remote_url ?? url($this->username . $suffix); } - public function localUrl($suffix = '') + public function localUrl($suffix = null) { - return url($this->username.$suffix); + return url($this->username . $suffix); } - public function permalink($suffix = '') + public function permalink($suffix = null) { - if($this->remote_url) { - return $this->remote_url; - } - return url('users/'.$this->username.$suffix); + return $this->remote_url ?? url('users/' . $this->username . $suffix); } public function emailUrl() From f39fec44f574ee903a76b4969221a084aa15d030 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 17 Dec 2018 23:40:35 -0700 Subject: [PATCH 07/30] Update Status model --- app/Status.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Status.php b/app/Status.php index 4769a733b..e9c40ca5f 100644 --- a/app/Status.php +++ b/app/Status.php @@ -90,6 +90,9 @@ class Status extends Model public function url() { + if($this->url) { + return $this->url; + } $id = $this->id; $username = $this->profile->username; $path = config('app.url')."/p/{$username}/{$id}"; From b85d351bc507e4a775d56b92aa0aabb3cbee2301 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 18 Dec 2018 00:09:36 -0700 Subject: [PATCH 08/30] Update avatar logic, allow custom file size limits. Fixes #654 --- .../Controllers/Api/BaseApiController.php | 2 +- app/Http/Controllers/AvatarController.php | 2 +- config/pixelfed.php | 10 +++++++ resources/views/settings/home.blade.php | 28 +++++++++++++++++-- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/BaseApiController.php b/app/Http/Controllers/Api/BaseApiController.php index ad717dfe6..c87dd1983 100644 --- a/app/Http/Controllers/Api/BaseApiController.php +++ b/app/Http/Controllers/Api/BaseApiController.php @@ -123,7 +123,7 @@ class BaseApiController extends Controller public function avatarUpdate(Request $request) { $this->validate($request, [ - 'upload' => 'required|mimes:jpeg,png,gif|max:2000', + 'upload' => 'required|mimes:jpeg,png,gif|max:'.config('pixelfed.max_avatar_size'), ]); try { diff --git a/app/Http/Controllers/AvatarController.php b/app/Http/Controllers/AvatarController.php index 31824abcb..f8a201b87 100644 --- a/app/Http/Controllers/AvatarController.php +++ b/app/Http/Controllers/AvatarController.php @@ -19,7 +19,7 @@ class AvatarController extends Controller public function store(Request $request) { $this->validate($request, [ - 'avatar' => 'required|mimes:jpeg,png|max:2000', + 'avatar' => 'required|mimes:jpeg,png|max:'.config('pixelfed.max_avatar_size'), ]); try { diff --git a/config/pixelfed.php b/config/pixelfed.php index 47023105e..a5ad0db28 100644 --- a/config/pixelfed.php +++ b/config/pixelfed.php @@ -107,6 +107,16 @@ return [ */ 'max_photo_size' => env('MAX_PHOTO_SIZE', 15000), + /* + |-------------------------------------------------------------------------- + | Avatar file size limit + |-------------------------------------------------------------------------- + | + | Update the max avatar size, in KB. + | + */ + 'max_avatar_size' => (int) env('MAX_AVATAR_SIZE', 2000), + /* |-------------------------------------------------------------------------- | Caption limit diff --git a/resources/views/settings/home.blade.php b/resources/views/settings/home.blade.php index b54e9bf2b..7a82d9a8b 100644 --- a/resources/views/settings/home.blade.php +++ b/resources/views/settings/home.blade.php @@ -10,11 +10,12 @@ @csrf
- +

{{Auth::user()->username}}

-

Change Profile Photo

+

Change Profile Photo

+

Max avatar size:

@@ -60,6 +61,25 @@

+
+

Storage Usage

+
+
+ +
+
+
+
+
+ + {{$storage['percentUsed']}}% used + + + {{$storage['usedPretty']}} / {{$storage['limitPretty']}} + +
+
+

@@ -96,6 +116,8 @@ $('.bio-counter').html(val); }); + $('#maxAvatarSize').text(filesize({{config('pixelfed.max_avatar_size') * 1024}}, {round: 0})); + $(document).on('click', '.change-profile-photo', function(e) { e.preventDefault(); swal({ @@ -103,7 +125,7 @@ content: { element: 'input', attributes: { - placeholder: 'Upload your photo', + placeholder: 'Upload your photo.', type: 'file', name: 'photoUpload', id: 'photoUploadInput' From ab0b39afeb591487b4b29409cc01ab90d317bb50 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 18 Dec 2018 00:13:24 -0700 Subject: [PATCH 09/30] Bump version to 0.7.0 --- config/pixelfed.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/pixelfed.php b/config/pixelfed.php index a5ad0db28..bfe7f17dd 100644 --- a/config/pixelfed.php +++ b/config/pixelfed.php @@ -23,7 +23,7 @@ return [ | This value is the version of your PixelFed instance. | */ - 'version' => '0.6.1', + 'version' => '0.7.0', /* |-------------------------------------------------------------------------- From 541ededfaa0fd8fc370d7fc6d641dc53282190ef Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Wed, 19 Dec 2018 18:52:46 -0700 Subject: [PATCH 10/30] Update nav, remove img-thumbnail from avatar to fix height bug --- resources/views/layouts/partial/nav.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/layouts/partial/nav.blade.php b/resources/views/layouts/partial/nav.blade.php index 8a3cb49c2..b311581aa 100644 --- a/resources/views/layouts/partial/nav.blade.php +++ b/resources/views/layouts/partial/nav.blade.php @@ -70,9 +70,9 @@ -@endsection \ No newline at end of file +@endsection + +@push('scripts') + +@endpush \ No newline at end of file From b5cc7b12bc19f2b15387091fc7c3d0348492aef8 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 20 Dec 2018 22:03:01 -0700 Subject: [PATCH 14/30] Update DeleteAccountJob --- .../DeletePipeline/DeleteAccountPipeline.php | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php index fa2913142..553eaa118 100644 --- a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php +++ b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php @@ -68,8 +68,12 @@ class DeleteAccountPipeline implements ShouldQueue $this->deleteMedia($user); $this->deleteMentions($user); $this->deleteNotifications($user); + $this->deleteStatuses($user); + $this->deleteReports($user); + $this->deleteProfile($user); + $this->deleteUser($user); - // todo send Delete to every known instance sharedInbox + // TODO: send Delete to every known instance sharedInbox } public function deleteAccountLogs($user) @@ -77,7 +81,7 @@ class DeleteAccountPipeline implements ShouldQueue AccountLog::chunk(200, function($logs) use ($user) { foreach($logs as $log) { if($log->user_id == $user->id) { - $log->delete(); + $log->forceDelete(); } } }); @@ -85,7 +89,7 @@ class DeleteAccountPipeline implements ShouldQueue public function deleteActivities($user) { - // todo after AP + // deprecated, removed inbox activity logger } public function deleteAvatar($user) @@ -100,35 +104,35 @@ class DeleteAccountPipeline implements ShouldQueue unlink($avatar->thumb_path); } - $avatar->delete(); + $avatar->forceDelete(); } public function deleteBookmarks($user) { - Bookmark::whereProfileId($user->profile->id)->delete(); + Bookmark::whereProfileId($user->profile->id)->forceDelete(); } public function deleteEmailVerification($user) { - EmailVerification::whereUserId($user->id)->delete(); + EmailVerification::whereUserId($user->id)->forceDelete(); } public function deleteFollowRequests($user) { $id = $user->profile->id; - FollowRequest::whereFollowingId($id)->orWhere('follower_id', $id)->delete(); + FollowRequest::whereFollowingId($id)->orWhere('follower_id', $id)->forceDelete(); } public function deleteFollowers($user) { $id = $user->profile->id; - Follower::whereProfileId($id)->orWhere('following_id', $id)->delete(); + Follower::whereProfileId($id)->orWhere('following_id', $id)->forceDelete(); } public function deleteLikes($user) { $id = $user->profile->id; - Like::whereProfileId($id)->delete(); + Like::whereProfileId($id)->forceDelete(); } public function deleteMedia($user) @@ -143,23 +147,36 @@ class DeleteAccountPipeline implements ShouldQueue if(is_file($thumb)) { unlink($thumb); } - $media->delete(); + $media->forceDelete(); } } public function deleteMentions($user) { - Mention::whereProfileId($user->profile->id)->delete(); + Mention::whereProfileId($user->profile->id)->forceDelete(); } public function deleteNotifications($user) { $id = $user->profile->id; - Notification::whereProfileId($id)->orWhere('actor_id', $id)->delete(); + Notification::whereProfileId($id)->orWhere('actor_id', $id)->forceDelete(); } - public function deleteProfile($user) {} - public function deleteReports($user) {} - public function deleteStatuses($user) {} - public function deleteUser($user) {} + public function deleteStatuses($user) { + Status::whereProfileId($user->profile->id)->forceDelete(); + } + + public function deleteProfile($user) { + Profile::whereUserId($user->id)->delete(); + } + + public function deleteReports($user) { + Report::whereUserId($user->id)->forceDelete(); + } + + public function deleteUser($user) { + UserFilter::find($user->id)->forceDelete(); + UserSetting::find($user->id)->forceDelete(); + User::find($user->id)->forceDelete(); + } } From 53fa1326d4346ea24489aef34f0b7fa9d935f8d0 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 20 Dec 2018 22:28:47 -0700 Subject: [PATCH 15/30] Update Help Center with account deletion instructions --- .../views/site/help/your-profile.blade.php | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/resources/views/site/help/your-profile.blade.php b/resources/views/site/help/your-profile.blade.php index d31bfe99d..7eb47a903 100644 --- a/resources/views/site/help/your-profile.blade.php +++ b/resources/views/site/help/your-profile.blade.php @@ -16,7 +16,7 @@
To create an account using a web browser: -
    +
    1. Go to {{route('settings')}}.
    2. You should see the Name, Website, and Bio fields.
    3. Change the desired fields, and then click the Submit button.
    4. @@ -45,7 +45,7 @@
      To change your account visibility: -
        +
        1. Go to {{route('settings.privacy')}}.
        2. Check the Private Account checkbox.
        3. The confirmation modal will popup and ask you if you want to keep existing followers and disable new follow requests
        4. @@ -99,7 +99,7 @@

      --}}
      -

      Security

      +

      Security

      +

      +
      +

      If you temporarily disable your account, your profile, photos, comments and likes will be hidden until you reactivate it by logging back in. To temporarily disable your account:

      +
        +
      1. Log into {{config('pixelfed.domain.app')}}
      2. +
      3. Tap or click the menu and select Settings
      4. +
      5. Scroll down and click on the Temporarily Disable Account link.
      6. +
      7. Follow the instructions on the next page.
      8. +
      +
      +
      +

      --}} +

      + +

      +
      + {{--
      +

      When you delete your account, your profile, photos, videos, comments, likes and followers will be permanently removed. If you'd just like to take a break, you can temporarily disable your account instead.

      +
      --}} +

      After you delete your account, you can't sign up again with the same username on this instance or add that username to another account on this instance, and we can't reactivate deleted accounts.

      +

      To permanently delete your account:

      +
        +
      1. Go to the Delete Your Account page. If you're not logged into pixelfed on the web, you'll be asked to log in first. You can't delete your account from within a mobile app.
      2. +
      3. Confirm your account password.
      4. +
      5. On the Delete Your Account page click or tap on the Permanently Delete My Account button.
      6. +
      +
      +
      +

      @endsection \ No newline at end of file From 51c3be37d3f407afa96cef062c17e8b4a9311133 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 20 Dec 2018 23:14:22 -0700 Subject: [PATCH 16/30] Update AdminController --- app/Http/Controllers/AdminController.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 8f04879eb..371d35857 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -3,19 +3,23 @@ namespace App\Http\Controllers; use App\Media; +use App\Like; use App\Profile; use App\Report; use App\Status; use App\User; use Carbon\Carbon; use Illuminate\Http\Request; +use Jackiedo\DotenvEditor\DotenvEditor; use App\Http\Controllers\Admin\{ - AdminReportController + AdminReportController, + AdminSettingsController }; +use App\Util\Lexer\PrettyNumber; class AdminController extends Controller { - use AdminReportController; + use AdminReportController, AdminSettingsController; public function __construct() { @@ -30,15 +34,16 @@ class AdminController extends Controller public function users(Request $request) { - $stats = []; - $users = User::orderBy('id', 'desc')->paginate(10); + $col = $request->query('col') ?? 'id'; + $dir = $request->query('dir') ?? 'desc'; + $stats = $this->collectUserStats($request); + $users = User::withCount('statuses')->orderBy($col, $dir)->paginate(10); return view('admin.users.home', compact('users', 'stats')); } - public function editUser(Request $request, $id) { - $user = User::find($id); + $user = User::findOrFail($id); $profile = $user->profile; return view('admin.users.edit', compact('user', 'profile')); } @@ -98,7 +103,7 @@ class AdminController extends Controller 'remote' => Profile::whereNotNull('remote_url')->count() ]; $stats['avg'] = [ - 'age' => 0, + 'likes' => floor(Like::average('profile_id')), 'posts' => floor(Status::avg('profile_id')) ]; return $stats; From 868a83cb655fe4fe2b59103f656d4084ebd5029b Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 20 Dec 2018 23:15:20 -0700 Subject: [PATCH 17/30] Update DeleteAccountPipeline job, add db transactions to prevent race conditions --- .../DeletePipeline/DeleteAccountPipeline.php | 175 +++++++----------- 1 file changed, 63 insertions(+), 112 deletions(-) diff --git a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php index 553eaa118..c0d0a6000 100644 --- a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php +++ b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php @@ -7,6 +7,7 @@ use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use DB; use App\{ AccountLog, Activity, @@ -57,126 +58,76 @@ class DeleteAccountPipeline implements ShouldQueue public function handle() { $user = $this->user; - $this->deleteAccountLogs($user); - $this->deleteActivities($user); - $this->deleteAvatar($user); - $this->deleteBookmarks($user); - $this->deleteEmailVerification($user); - $this->deleteFollowRequests($user); - $this->deleteFollowers($user); - $this->deleteLikes($user); - $this->deleteMedia($user); - $this->deleteMentions($user); - $this->deleteNotifications($user); - $this->deleteStatuses($user); - $this->deleteReports($user); - $this->deleteProfile($user); - $this->deleteUser($user); - - // TODO: send Delete to every known instance sharedInbox - } - - public function deleteAccountLogs($user) - { - AccountLog::chunk(200, function($logs) use ($user) { - foreach($logs as $log) { - if($log->user_id == $user->id) { - $log->forceDelete(); + DB::transaction(function() use ($user) { + AccountLog::chunk(200, function($logs) use ($user) { + foreach($logs as $log) { + if($log->user_id == $user->id) { + $log->forceDelete(); + } } + }); + + if($user->profile) { + $avatar = $user->profile->avatar; + + if(is_file($avatar->media_path)) { + unlink($avatar->media_path); + } + + if(is_file($avatar->thumb_path)) { + unlink($avatar->thumb_path); + } + $avatar->forceDelete(); } + + Bookmark::whereProfileId($user->profile->id)->forceDelete(); + + EmailVerification::whereUserId($user->id)->forceDelete(); + + $id = $user->profile->id; + FollowRequest::whereFollowingId($id)->orWhere('follower_id', $id)->forceDelete(); + + Follower::whereProfileId($id)->orWhere('following_id', $id)->forceDelete(); + + Like::whereProfileId($id)->forceDelete(); + + $medias = Media::whereUserId($user->id)->get(); + foreach($medias as $media) { + $path = $media->media_path; + $thumb = $media->thumbnail_path; + if(is_file($path)) { + unlink($path); + } + if(is_file($thumb)) { + unlink($thumb); + } + $media->forceDelete(); + } + + Mention::whereProfileId($user->profile->id)->forceDelete(); + + Notification::whereProfileId($id)->orWhere('actor_id', $id)->forceDelete(); + + Status::whereProfileId($user->profile->id)->forceDelete(); + + Report::whereUserId($user->id)->forceDelete(); + $this->deleteProfile($user); }); } - public function deleteActivities($user) - { - // deprecated, removed inbox activity logger - } - - public function deleteAvatar($user) - { - $avatar = $user->profile->avatar; - - if(is_file($avatar->media_path)) { - unlink($avatar->media_path); - } - - if(is_file($avatar->thumb_path)) { - unlink($avatar->thumb_path); - } - - $avatar->forceDelete(); - } - - public function deleteBookmarks($user) - { - Bookmark::whereProfileId($user->profile->id)->forceDelete(); - } - - public function deleteEmailVerification($user) - { - EmailVerification::whereUserId($user->id)->forceDelete(); - } - - public function deleteFollowRequests($user) - { - $id = $user->profile->id; - FollowRequest::whereFollowingId($id)->orWhere('follower_id', $id)->forceDelete(); - } - - public function deleteFollowers($user) - { - $id = $user->profile->id; - Follower::whereProfileId($id)->orWhere('following_id', $id)->forceDelete(); - } - - public function deleteLikes($user) - { - $id = $user->profile->id; - Like::whereProfileId($id)->forceDelete(); - } - - public function deleteMedia($user) - { - $medias = Media::whereUserId($user->id)->get(); - foreach($medias as $media) { - $path = $media->media_path; - $thumb = $media->thumbnail_path; - if(is_file($path)) { - unlink($path); - } - if(is_file($thumb)) { - unlink($thumb); - } - $media->forceDelete(); - } - } - - public function deleteMentions($user) - { - Mention::whereProfileId($user->profile->id)->forceDelete(); - } - - public function deleteNotifications($user) - { - $id = $user->profile->id; - Notification::whereProfileId($id)->orWhere('actor_id', $id)->forceDelete(); - } - - public function deleteStatuses($user) { - Status::whereProfileId($user->profile->id)->forceDelete(); - } - public function deleteProfile($user) { - Profile::whereUserId($user->id)->delete(); - } - - public function deleteReports($user) { - Report::whereUserId($user->id)->forceDelete(); + DB::transaction(function() use ($user) { + Profile::whereUserId($user->id)->delete(); + $this->deleteUser($user); + }); } public function deleteUser($user) { - UserFilter::find($user->id)->forceDelete(); - UserSetting::find($user->id)->forceDelete(); - User::find($user->id)->forceDelete(); + + DB::transaction(function() use ($user) { + UserFilter::whereUserId($user->id)->forceDelete(); + UserSetting::whereUserId($user->id)->forceDelete(); + $user->forceDelete(); + }); } } From 251cb72aa41edd0257b8e4a27e7215bb0e1c044e Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 20 Dec 2018 23:18:06 -0700 Subject: [PATCH 18/30] Update SettingsController --- app/Http/Controllers/SettingsController.php | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index ea3c7ab28..9f4f116fe 100644 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -15,6 +15,7 @@ use App\Http\Controllers\Settings\{ PrivacySettings, SecuritySettings }; +use App\Jobs\DeletePipeline\DeleteAccountPipeline; class SettingsController extends Controller { @@ -43,7 +44,7 @@ class SettingsController extends Controller 'optimize_screen_reader', 'high_contrast_mode', 'video_autoplay', - ]; + ]; foreach ($fields as $field) { $form = $request->input($field); if ($form == 'on') { @@ -130,5 +131,26 @@ class SettingsController extends Controller { return view('settings.developers'); } + + public function removeAccountTemporary(Request $request) + { + return view('settings.remove.temporary'); + } + + public function removeAccountPermanent(Request $request) + { + return view('settings.remove.permanent'); + } + + public function removeAccountPermanentSubmit(Request $request) + { + $user = Auth::user(); + if($user->is_admin == true) { + return abort(400, 'You cannot delete an admin account.'); + } + DeleteAccountPipeline::dispatch($user); + Auth::logout(); + return redirect('/'); + } } From 2f354c5bc3b167cdb0a494bf48bd5bd0b2433734 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 20 Dec 2018 23:18:48 -0700 Subject: [PATCH 19/30] Update web routes --- routes/web.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/routes/web.php b/routes/web.php index cbd2f5a9d..b2564ae0e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -132,6 +132,12 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::post('privacy/blocked-users', 'SettingsController@blockedUsersUpdate')->middleware('throttle:100,1440'); Route::get('privacy/blocked-instances', 'SettingsController@blockedInstances')->name('settings.privacy.blocked-instances'); + Route::group(['prefix' => 'remove', 'middleware' => 'dangerzone'], function() { + // Route::get('request/temporary', 'SettingsController@removeAccountTemporary')->name('settings.remove.temporary'); + Route::get('request/permanent', 'SettingsController@removeAccountPermanent')->name('settings.remove.permanent'); + Route::post('request/permanent', 'SettingsController@removeAccountPermanentSubmit'); + }); + Route::group(['prefix' => 'security', 'middleware' => 'dangerzone'], function() { Route::get( '/', From 8c121518596b4b69d7f1cebdab2637e93297ae37 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 21 Dec 2018 12:51:25 -0700 Subject: [PATCH 20/30] Update Follower model --- app/Follower.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Follower.php b/app/Follower.php index 322b29a0f..8d147d10e 100644 --- a/app/Follower.php +++ b/app/Follower.php @@ -26,7 +26,7 @@ class Follower extends Model public function permalink($append = null) { - $path = $this->actor->permalink("/follow/{$this->id}{$append}"); + $path = $this->actor->permalink("#accepts/follows/{$this->id}{$append}"); return url($path); } From 591e50541e39cb71a454ef2771c91b61ef4e3617 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 21 Dec 2018 12:51:52 -0700 Subject: [PATCH 21/30] Update CommentController --- app/Http/Controllers/CommentController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 4c2377cc2..2c273527d 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -35,9 +35,9 @@ class CommentController extends Controller abort(403); } $this->validate($request, [ - 'item' => 'required|integer', - 'comment' => 'required|string|max:500', - ]); + 'item' => 'required|integer', + 'comment' => 'required|string|max:500', + ]); $comment = $request->input('comment'); $statusId = $request->item; From c819bfe351b5d9fe8385a36bc46bb592741014ff Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 21 Dec 2018 12:53:10 -0700 Subject: [PATCH 22/30] Update DiscoverController --- app/Http/Controllers/DiscoverController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/DiscoverController.php b/app/Http/Controllers/DiscoverController.php index 66d515564..a39dfd4b1 100644 --- a/app/Http/Controllers/DiscoverController.php +++ b/app/Http/Controllers/DiscoverController.php @@ -36,6 +36,8 @@ class DiscoverController extends Controller ->firstOrFail(); $posts = $tag->posts() + ->whereNull('url') + ->whereNull('uri') ->whereHas('media') ->withCount(['likes', 'comments']) ->whereIsNsfw(false) From abc738a532e356f2cc7773584f97232abc847166 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 21 Dec 2018 12:54:14 -0700 Subject: [PATCH 23/30] Update AuthLogin listener --- app/Listeners/AuthLogin.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Listeners/AuthLogin.php b/app/Listeners/AuthLogin.php index 6d3f9e2db..a69f86f81 100644 --- a/app/Listeners/AuthLogin.php +++ b/app/Listeners/AuthLogin.php @@ -25,6 +25,11 @@ class AuthLogin public function handle($event) { $user = $event->user; + + if(!$user) { + return; + } + if (empty($user->settings)) { DB::transaction(function() use($user) { UserSetting::firstOrCreate([ From da68aa1c266a00417637cd735ac3d5de6a2a3ef4 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 21 Dec 2018 12:54:27 -0700 Subject: [PATCH 24/30] Update Notification model --- app/Notification.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Notification.php b/app/Notification.php index cd4212b15..ae5cd567f 100644 --- a/app/Notification.php +++ b/app/Notification.php @@ -16,6 +16,8 @@ class Notification extends Model */ protected $dates = ['deleted_at']; + protected $fillable = ['*']; + public function actor() { return $this->belongsTo(Profile::class, 'actor_id', 'id'); From b96e71c0ae4a55557f196e373f09ea6863e427e8 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 21 Dec 2018 12:56:56 -0700 Subject: [PATCH 25/30] Update AP Like transformer --- app/Transformer/ActivityPub/Verb/Like.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Transformer/ActivityPub/Verb/Like.php b/app/Transformer/ActivityPub/Verb/Like.php index 5ddebe9a8..5662ab758 100644 --- a/app/Transformer/ActivityPub/Verb/Like.php +++ b/app/Transformer/ActivityPub/Verb/Like.php @@ -7,13 +7,13 @@ use League\Fractal; class Like extends Fractal\TransformerAbstract { - public function transform(LikeModel $like) - { - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'type' => 'Like', - 'actor' => $like->actor->permalink(), - 'object' => $like->status->url() - ]; - } + public function transform(LikeModel $like) + { + return [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Like', + 'actor' => $like->actor->permalink(), + 'object' => $like->status->url() + ]; + } } \ No newline at end of file From e4a7aa00a5f719be9028bb864fd43bba2ce40216 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 21 Dec 2018 12:57:43 -0700 Subject: [PATCH 26/30] Update AP helpers --- app/Util/ActivityPub/Helpers.php | 70 ++++++-------------------------- 1 file changed, 12 insertions(+), 58 deletions(-) diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index e6f52530d..2da8c09f8 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -22,6 +22,7 @@ use App\Jobs\ImageOptimizePipeline\{ImageOptimize,ImageThumbnail}; use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Util\HttpSignatures\{GuzzleHttpSignatures, KeyStore, Context, Verifier}; use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; +use App\Util\ActivityPub\HttpSignature; class Helpers { @@ -215,13 +216,14 @@ class Helpers { } else { $reply_to = null; } - + $ts = is_array($res['published']) ? $res['published'][0] : $res['published']; $status = new Status; $status->profile_id = $profile->id; $status->url = $url; + $status->uri = $url; $status->caption = strip_tags($res['content']); $status->rendered = Purify::clean($res['content']); - $status->created_at = Carbon::parse($res['published']); + $status->created_at = Carbon::parse($ts); $status->in_reply_to_id = $reply_to; $status->local = false; $status->save(); @@ -307,72 +309,24 @@ class Helpers { public static function sendSignedObject($senderProfile, $url, $body) { - $profile = $senderProfile; - $keyId = $profile->keyId(); + $payload = json_encode($body); + $headers = HttpSignature::sign($senderProfile, $url, $body); - $date = new \DateTime('UTC'); - $date = $date->format('D, d M Y H:i:s \G\M\T'); - $host = parse_url($url, PHP_URL_HOST); - $path = parse_url($url, PHP_URL_PATH); - $headers = [ - 'date' => $date, - 'host' => $host, - 'content-type' => 'application/activity+json', - ]; - - $context = new Context([ - 'keys' => [$profile->keyId() => $profile->private_key], - 'algorithm' => 'rsa-sha256', - 'headers' => ['(request-target)', 'date', 'host', 'content-type'], - ]); - - $handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context); - $client = new Client(['handler' => $handlerStack]); - - $response = $client->request('POST', $url, ['headers' => $headers, 'json' => $body]); + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HEADER, true); + $response = curl_exec($ch); return; } private static function _headersToSigningString($headers) { - return implode("\n", array_map(function($k, $v){ - return strtolower($k).': '.$v; - }, array_keys($headers), $headers)); } public static function validateSignature($request, $payload = null) { - $date = Carbon::parse($request['date']); - $min = Carbon::now()->subHours(13); - $max = Carbon::now()->addHours(13); - if($date->gt($min) == false || $date->lt($max) == false) { - return false; - } - $json = json_encode($payload); - $digest = base64_encode(hash('sha256', $json, true)); - $parts = explode(',', $request['signature']); - $signatureData = []; - foreach($parts as $part) { - if(preg_match('/(.+)="(.+)"/', $part, $match)) { - $signatureData[$match[1]] = $match[2]; - } - } - - $actor = $payload['actor']; - $profile = self::profileFirstOrNew($actor, true); - if(!$profile) { - return false; - } - $publicKey = $profile->public_key; - $path = $request['path']; - $host = $request['host']; - $signingString = "(request-target): post {$path}".PHP_EOL. - "host: {$host}".PHP_EOL. - "date: {$request['date']}".PHP_EOL. - "digest: {$request['digest']}".PHP_EOL. - "content-type: {$request['contentType']}"; - $verified = openssl_verify($signingString, base64_decode($signatureData['signature']), $publicKey, OPENSSL_ALGO_SHA256); - return (bool) $verified; } public static function fetchPublicKey() From 476e9ff8b39aa91dfcbf48d93c56ece6e7a045e1 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 23 Dec 2018 17:12:08 -0700 Subject: [PATCH 27/30] Update routes --- routes/web.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/routes/web.php b/routes/web.php index b2564ae0e..9356e4c9a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -132,11 +132,13 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::post('privacy/blocked-users', 'SettingsController@blockedUsersUpdate')->middleware('throttle:100,1440'); Route::get('privacy/blocked-instances', 'SettingsController@blockedInstances')->name('settings.privacy.blocked-instances'); - Route::group(['prefix' => 'remove', 'middleware' => 'dangerzone'], function() { - // Route::get('request/temporary', 'SettingsController@removeAccountTemporary')->name('settings.remove.temporary'); - Route::get('request/permanent', 'SettingsController@removeAccountPermanent')->name('settings.remove.permanent'); - Route::post('request/permanent', 'SettingsController@removeAccountPermanentSubmit'); - }); + // Todo: Release in 0.7.2 + // Route::group(['prefix' => 'remove', 'middleware' => 'dangerzone'], function() { + // Route::get('request/temporary', 'SettingsController@removeAccountTemporary')->name('settings.remove.temporary'); + // Route::post('request/temporary', 'SettingsController@removeAccountTemporarySubmit'); + // Route::get('request/permanent', 'SettingsController@removeAccountPermanent')->name('settings.remove.permanent'); + // Route::post('request/permanent', 'SettingsController@removeAccountPermanentSubmit'); + // }); Route::group(['prefix' => 'security', 'middleware' => 'dangerzone'], function() { Route::get( From e39f88f6c6804fe261f0bb7e334deb93d142bc32 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 23 Dec 2018 17:16:59 -0700 Subject: [PATCH 28/30] Bump version to 0.7.1 --- app/Console/Commands/FixUsernames.php | 110 ++++++++++++++++++ .../Controllers/Auth/RegisterController.php | 5 +- config/pixelfed.php | 2 +- 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/FixUsernames.php diff --git a/app/Console/Commands/FixUsernames.php b/app/Console/Commands/FixUsernames.php new file mode 100644 index 000000000..04f628edd --- /dev/null +++ b/app/Console/Commands/FixUsernames.php @@ -0,0 +1,110 @@ +info('This command is only for versions lower than 0.7.2'); + return; + } + + $this->info('Collecting data ...'); + + $affected = collect([]); + + $users = User::chunk(100, function($users) use($affected) { + foreach($users as $user) { + $val = str_replace(['-', '_'], '', $user->username); + if(!ctype_alnum($val)) { + $this->info('Found invalid username: ' . $user->username); + $affected->push($user); + } + } + }); + if($affected->count() > 0) { + $this->info('Found: ' . $affected->count() . ' affected usernames'); + + $opts = [ + 'Random replace (assigns random username)', + 'Best try replace (assigns alpha numeric username)', + 'Manual replace (manually set username)' + ]; + + foreach($affected as $u) { + $old = $u->username; + $opt = $this->choice('Select fix method:', $opts, 0); + + switch ($opt) { + case $opts[0]: + $new = "user_" . str_random(6); + $this->info('New username: ' . $new); + break; + + case $opts[1]: + $new = filter_var($old, FILTER_SANITIZE_STRING|FILTER_FLAG_STRIP_LOW); + if(strlen($new) < 6) { + $new = $new . '_' . str_random(4); + } + $this->info('New username: ' . $new); + break; + + case $opts[2]: + $new = $this->ask('Enter new username:'); + $this->info('New username: ' . $new); + break; + + default: + $new = "user_" . str_random(6); + break; + } + + DB::transaction(function() use($u, $new) { + $profile = $u->profile; + $profile->username = $new; + $u->username = $new; + $u->save(); + $profile->save(); + }); + $this->info('Selected: ' . $opt); + } + + $this->info('Fixed ' . $affected->count() . ' usernames!'); + } + } +} diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 9a8ef07a1..d9354697a 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -55,7 +55,6 @@ class RegisterController extends Controller $this->validateUsername($data['username']); $usernameRules = [ 'required', - 'alpha_dash', 'min:2', 'max:15', 'unique:users', @@ -63,6 +62,10 @@ class RegisterController extends Controller if (!ctype_alpha($value[0])) { return $fail($attribute.' is invalid. Username must be alpha-numeric and start with a letter.'); } + $val = str_replace(['-', '_'], '', $value); + if(!ctype_alnum($val)) { + return $fail($attribute . ' is invalid. Username must be alpha-numeric.'); + } }, ]; diff --git a/config/pixelfed.php b/config/pixelfed.php index bfe7f17dd..3fa1c3690 100644 --- a/config/pixelfed.php +++ b/config/pixelfed.php @@ -23,7 +23,7 @@ return [ | This value is the version of your PixelFed instance. | */ - 'version' => '0.7.0', + 'version' => '0.7.1', /* |-------------------------------------------------------------------------- From a73c7f48eede8253727cefcf218394870f5c3b0e Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 23 Dec 2018 17:48:41 -0700 Subject: [PATCH 29/30] Update AdminController --- app/Http/Controllers/AdminController.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 371d35857..d4a7d2424 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -11,15 +11,12 @@ use App\User; use Carbon\Carbon; use Illuminate\Http\Request; use Jackiedo\DotenvEditor\DotenvEditor; -use App\Http\Controllers\Admin\{ - AdminReportController, - AdminSettingsController -}; +use App\Http\Controllers\Admin\AdminReportController; use App\Util\Lexer\PrettyNumber; class AdminController extends Controller { - use AdminReportController, AdminSettingsController; + use AdminReportController; public function __construct() { From b86eb83cc06a797c6ae81030450b937cb78029e2 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 23 Dec 2018 17:52:16 -0700 Subject: [PATCH 30/30] Update Help Center --- resources/views/site/help/your-profile.blade.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/views/site/help/your-profile.blade.php b/resources/views/site/help/your-profile.blade.php index 7eb47a903..7354585ea 100644 --- a/resources/views/site/help/your-profile.blade.php +++ b/resources/views/site/help/your-profile.blade.php @@ -140,9 +140,9 @@

-
+ {{--

Delete Your Account

- {{--

+

- {{--
+

When you delete your account, your profile, photos, videos, comments, likes and followers will be permanently removed. If you'd just like to take a break, you can temporarily disable your account instead.

-
--}} +

After you delete your account, you can't sign up again with the same username on this instance or add that username to another account on this instance, and we can't reactivate deleted accounts.

To permanently delete your account:

    @@ -178,5 +178,5 @@
-

+

--}} @endsection \ No newline at end of file