diff --git a/.env.testing b/.env.testing index f7dcfe55e..a37e329eb 100644 --- a/.env.testing +++ b/.env.testing @@ -53,3 +53,5 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_APP_URL="${APP_URL}" MIX_API_BASE="${API_BASE}" MIX_API_SEARCH="${API_SEARCH}" + +TELESCOPE_ENABLED=false diff --git a/README.md b/README.md index 4b9684e9b..a2520a39e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ there is a stable release**. The following setup instructions are intended for testing and development. ## Requirements - - PHP >= 7.1.3 (7.2+ recommended for stable version) + - PHP >= 7.1.3 < 7.3 (7.2.x recommended for stable version) - MySQL >= 5.7, Postgres (MariaDB and sqlite are not supported yet) - Redis - Composer diff --git a/app/Console/Commands/FixUsernames.php b/app/Console/Commands/FixUsernames.php index 04f628edd..3e59abe54 100644 --- a/app/Console/Commands/FixUsernames.php +++ b/app/Console/Commands/FixUsernames.php @@ -39,11 +39,6 @@ class FixUsernames extends Command */ public function handle() { - if(version_compare(config('pixelfed.version'), '0.7.2') !== -1) { - $this->info('This command is only for versions lower than 0.7.2'); - return; - } - $this->info('Collecting data ...'); $affected = collect([]); diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 87423c5aa..980dd4dcd 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -339,6 +339,11 @@ class AccountController extends Controller $request->session()->push('2fa.session.active', true); return redirect('/'); } else { + + if($this->twoFactorBackupCheck($request, $code, $user)) { + return redirect('/'); + } + if($request->session()->has('2fa.attempts')) { $count = (int) $request->session()->has('2fa.attempts'); $request->session()->push('2fa.attempts', $count + 1); @@ -350,4 +355,31 @@ class AccountController extends Controller ]); } } + + protected function twoFactorBackupCheck($request, $code, User $user) + { + $backupCodes = $user->{'2fa_backup_codes'}; + if($backupCodes) { + $codes = json_decode($backupCodes, true); + foreach ($codes as $c) { + if(hash_equals($c, $code)) { + // remove code + $codes = array_flatten(array_diff($codes, [$code])); + $user->{'2fa_backup_codes'} = json_encode($codes); + $user->save(); + $request->session()->push('2fa.session.active', true); + return true; + } else { + return false; + } + } + } else { + return false; + } + } + + public function accountRestored(Request $request) + { + // + } } diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php index 2511b9984..508874341 100644 --- a/app/Http/Controllers/FederationController.php +++ b/app/Http/Controllers/FederationController.php @@ -181,24 +181,75 @@ XML; return ProfileController::accountCheck($profile); } $body = $request->getContent(); - $bodyDecoded = json_decode($body, true); + $bodyDecoded = json_decode($body, true, 8); + if($this->verifySignature($request, $profile) == true) { + InboxWorker::dispatch($request->headers->all(), $profile, $bodyDecoded); + } else if($this->blindKeyRotation($request, $profile) == true) { + InboxWorker::dispatch($request->headers->all(), $profile, $bodyDecoded); + } else { + abort(400, 'Bad Signature'); + } + return; + } + + protected function verifySignature(Request $request, Profile $profile) + { + $body = $request->getContent(); + $bodyDecoded = json_decode($body, true, 8); $signature = $request->header('signature'); if(!$signature) { abort(400, 'Missing signature header'); } $signatureData = HttpSignature::parseSignatureHeader($signature); - $actor = Profile::whereKeyId($signatureData['keyId'])->first(); + $keyId = Helpers::validateUrl($signatureData['keyId']); + $id = Helpers::validateUrl($bodyDecoded['id']); + $keyDomain = parse_url($keyId, PHP_URL_HOST); + $idDomain = parse_url($id, PHP_URL_HOST); + if(isset($bodyDecoded['object']) + && is_array($bodyDecoded['object']) + && isset($bodyDecoded['object']['attributedTo']) + ) { + if(parse_url($bodyDecoded['object']['attributedTo'], PHP_URL_HOST) !== $keyDomain) { + abort(400, 'Invalid request'); + } + } + if(!$keyDomain || !$idDomain || $keyDomain !== $idDomain) { + abort(400, 'Invalid request'); + } + $actor = Profile::whereKeyId($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.'); + if($verified == 1) { + return true; + } else { + return false; } - InboxWorker::dispatch($request->headers->all(), $profile, $bodyDecoded); - return; + } + + protected function blindKeyRotation(Request $request, Profile $profile) + { + $signature = $request->header('signature'); + if(!$signature) { + abort(400, 'Missing signature header'); + } + $signatureData = HttpSignature::parseSignatureHeader($signature); + $keyId = Helpers::validateUrl($signatureData['keyId']); + $actor = Profile::whereKeyId($keyId)->first(); + $res = Zttp::timeout(5)->withHeaders([ + 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org', + ])->get($actor->remote_url); + $res = json_decode($res->body(), true, 8); + if($res['publicKey']['id'] !== $actor->key_id) { + return false; + } + $actor->public_key = $res['publicKey']['publicKeyPem']; + $actor->save(); + return $this->verifySignature($request, $profile); } public function userFollowing(Request $request, $username) diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index 86bbe0407..51fe32eee 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -300,7 +300,7 @@ class PublicApiController extends Controller ->whereNotIn('profile_id', $filtered) ->whereNull('in_reply_to_id') ->whereNull('reblog_of_id') - ->whereVisibility('public') + ->whereIn('visibility',['public', 'unlisted', 'private']) ->withCount(['comments', 'likes']) ->orderBy('created_at', 'desc') ->limit($limit) @@ -311,7 +311,7 @@ class PublicApiController extends Controller ->whereNotIn('profile_id', $filtered) ->whereNull('in_reply_to_id') ->whereNull('reblog_of_id') - ->whereVisibility('public') + ->whereIn('visibility',['public', 'unlisted', 'private']) ->withCount(['comments', 'likes']) ->orderBy('created_at', 'desc') ->simplePaginate($limit); diff --git a/app/Http/Controllers/Settings/SecuritySettings.php b/app/Http/Controllers/Settings/SecuritySettings.php index 99547b73b..5d1c49ad3 100644 --- a/app/Http/Controllers/Settings/SecuritySettings.php +++ b/app/Http/Controllers/Settings/SecuritySettings.php @@ -110,6 +110,19 @@ trait SecuritySettings return view('settings.security.2fa.recovery-codes', compact('user', 'codes')); } + public function securityTwoFactorRecoveryCodesRegenerate(Request $request) + { + $user = Auth::user(); + + if(!$user->{'2fa_enabled'} || !$user->{'2fa_secret'}) { + abort(403); + } + $backups = $this->generateBackupCodes(); + $user->{'2fa_backup_codes'} = json_encode($backups); + $user->save(); + return redirect(route('settings.security.2fa.recovery')); + } + public function securityTwoFactorUpdate(Request $request) { $user = Auth::user(); diff --git a/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php b/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php index 1100f0988..12a66c373 100644 --- a/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php +++ b/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php @@ -38,7 +38,7 @@ class StatusActivityPubDeliver implements ShouldQueue { $status = $this->status; - if($status->local == true || $status->url || $status->uri) { + if($status->local == false || $status->url || $status->uri) { return; } diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index b1cd2909e..3a73c1a95 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -210,6 +210,18 @@ class Helpers { $activity = ['object' => $res]; } + $idDomain = parse_url($res['id'], PHP_URL_HOST); + $urlDomain = parse_url($url, PHP_URL_HOST); + $actorDomain = parse_url($activity['object']['attributedTo'], PHP_URL_HOST); + + if( + $idDomain !== $urlDomain || + $actorDomain !== $urlDomain || + $idDomain !== $actorDomain + ) { + abort(400, 'Invalid object'); + } + $profile = self::profileFirstOrNew($activity['object']['attributedTo']); if(isset($activity['object']['inReplyTo']) && !empty($activity['object']['inReplyTo']) && $replyTo == true) { $reply_to = self::statusFirstOrFetch($activity['object']['inReplyTo'], false); diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 7791fe977..2d764f040 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -167,12 +167,13 @@ class Inbox return; } - $status = DB::transaction(function() use($activity, $actor) { + $status = DB::transaction(function() use($activity, $actor, $url) { $caption = str_limit(strip_tags($activity['content']), config('pixelfed.max_caption_length')); $status = new Status; $status->profile_id = $actor->id; $status->caption = $caption; $status->visibility = $status->scope = 'public'; + $status->uri = $url; $status->url = $url; $status->save(); return $status; @@ -219,7 +220,7 @@ class Inbox // send Accept to remote profile $accept = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $target->permalink().'#accepts/follows/', + 'id' => $target->permalink().'#accepts/follows/' . $follower->id, 'type' => 'Accept', 'actor' => $target->permalink(), 'object' => [ diff --git a/app/Util/ActivityPub/Validator/Accept.php b/app/Util/ActivityPub/Validator/Accept.php new file mode 100644 index 000000000..7230a37f3 --- /dev/null +++ b/app/Util/ActivityPub/Validator/Accept.php @@ -0,0 +1,32 @@ + 'required', + 'id' => 'required|string', + 'type' => [ + 'required', + Rule::in(['Accept']) + ], + 'actor' => 'required|url|active_url', + 'object' => 'required', + 'object.id' => 'required|url|active_url', + 'object.type' => [ + 'required', + Rule::in(['Follow']) + ], + 'object.actor' => 'required|url|active_url', + 'object.object' => 'required|url|active_url|same:actor', + ])->passes(); + + return $valid; + } +} \ No newline at end of file diff --git a/app/Util/Lexer/RestrictedNames.php b/app/Util/Lexer/RestrictedNames.php index 0bd2648b6..5fe8d4d38 100644 --- a/app/Util/Lexer/RestrictedNames.php +++ b/app/Util/Lexer/RestrictedNames.php @@ -39,7 +39,6 @@ class RestrictedNames 'ftp', 'guest', 'guests', - 'help', 'hostmaster', 'hostmaster', 'image', @@ -94,9 +93,6 @@ class RestrictedNames 'ssladmin', 'ssladministrator', 'sslwebmaster', - 'status', - 'support', - 'support', 'sys', 'sysadmin', 'system', @@ -107,7 +103,6 @@ class RestrictedNames 'uucp', 'webmaster', 'wpad', - 'www', ]; public static $reserved = [ @@ -126,36 +121,60 @@ class RestrictedNames 'account', 'api', 'auth', + 'broadcast', + 'broadcaster', 'css', 'checkpoint', + 'collection', + 'collections', 'c', - 'i', + 'cdn', 'dashboard', 'deck', 'discover', 'docs', + 'error', + 'explore', 'fonts', 'home', + 'help', + 'helpcenter', + 'i', 'img', 'js', + 'live', 'login', 'logout', 'media', + 'official', 'p', 'password', + 'reset', 'report', 'reports', + 'robot', + 'robots', 'search', + 'send', 'settings', + 'status', 'statuses', 'site', 'sites', + 'static', + 'story', + 'stories', + 'support', + 'telescope', 'timeline', 'timelines', 'tour', 'user', 'users', 'vendor', + 'ws', + 'wss', + 'www', '400', '401', '403', diff --git a/config/pixelfed.php b/config/pixelfed.php index b0118cd0e..feb3ed318 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.2', + 'version' => '0.7.7', /* |-------------------------------------------------------------------------- @@ -59,7 +59,7 @@ return [ */ 'restricted_names' => [ 'reserved_routes' => true, - 'use_blacklist' => false, + 'use_blacklist' => env('USERNAME_BLACKLIST', false), ], /* diff --git a/contrib/docker/Dockerfile.apache b/contrib/docker/Dockerfile.apache index 7f4b8742d..1bd5961e0 100644 --- a/contrib/docker/Dockerfile.apache +++ b/contrib/docker/Dockerfile.apache @@ -1,4 +1,4 @@ -FROM php:7-apache +FROM php:7.2-apache ARG COMPOSER_VERSION="1.6.5" ARG COMPOSER_CHECKSUM="67bebe9df9866a795078bb2cf21798d8b0214f2e0b2fd81f2e907a8ef0be3434" diff --git a/contrib/docker/Dockerfile.fpm b/contrib/docker/Dockerfile.fpm index 6c7822ea0..93676b10e 100644 --- a/contrib/docker/Dockerfile.fpm +++ b/contrib/docker/Dockerfile.fpm @@ -1,4 +1,4 @@ -FROM php:7-fpm +FROM php:7.2-fpm ARG COMPOSER_VERSION="1.6.5" ARG COMPOSER_CHECKSUM="67bebe9df9866a795078bb2cf21798d8b0214f2e0b2fd81f2e907a8ef0be3434" diff --git a/public/js/components.js b/public/js/components.js index 176e33265..1a5ce5926 100644 Binary files a/public/js/components.js and b/public/js/components.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 54baaeb40..44662a12a 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/components.js b/resources/assets/js/components.js index 50aa36bda..8d23cbf5a 100644 --- a/resources/assets/js/components.js +++ b/resources/assets/js/components.js @@ -25,6 +25,13 @@ pixelfed.readmore = () => { }); }; +try { + document.createEvent("TouchEvent"); + $('body').addClass('touch'); +} catch (e) { + return false; +} + window.InfiniteScroll = require('infinite-scroll'); window.filesize = require('filesize'); window.Plyr = require('plyr'); @@ -55,7 +62,7 @@ require('./components/statusform'); // }); // } -// Initalize Notification Helper +// Initialize Notification Helper window.pixelfed.n = {}; Vue.component( @@ -137,10 +144,10 @@ window.pixelfed.copyToClipboard = (str) => { const el = document.createElement('textarea'); el.value = str; el.setAttribute('readonly', ''); - el.style.position = 'absolute'; + el.style.position = 'absolute'; el.style.left = '-9999px'; document.body.appendChild(el); - const selected = + const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; @@ -162,4 +169,4 @@ $(document).ready(function() { const warningTitleCSS = 'color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;'; const warningDescCSS = 'font-size: 18px;'; console.log('%cStop!', warningTitleCSS); -console.log("%cThis is a browser feature intended for developers. If someone told you to copy and paste something here to enable a Pixelfed feature or \"hack\" someone's account, it is a scam and will give them access to your Pixelfed account.", warningDescCSS); \ No newline at end of file +console.log("%cThis is a browser feature intended for developers. If someone told you to copy and paste something here to enable a Pixelfed feature or \"hack\" someone's account, it is a scam and will give them access to your Pixelfed account.", warningDescCSS); diff --git a/resources/assets/js/components/DiscoverComponent.vue b/resources/assets/js/components/DiscoverComponent.vue index 974234b45..7cec33b60 100644 --- a/resources/assets/js/components/DiscoverComponent.vue +++ b/resources/assets/js/components/DiscoverComponent.vue @@ -70,7 +70,7 @@ export default { }).catch(err => { swal( 'Whoops! Something went wrong...', - 'An error occured, please try again later.', + 'An error occurred, please try again later.', 'error' ); }); diff --git a/resources/assets/js/components/PostComments.vue b/resources/assets/js/components/PostComments.vue index cc57ad699..6122f0b21 100644 --- a/resources/assets/js/components/PostComments.vue +++ b/resources/assets/js/components/PostComments.vue @@ -104,7 +104,7 @@ export default { $('.postCommentsLoader .lds-ring') .attr('style','width:100%') .addClass('pt-4 font-weight-bold text-muted') - .text('An error occured, cannot fetch comments. Please try again later.'); + .text('An error occurred, cannot fetch comments. Please try again later.'); } else { switch(error.response.status) { case 401: @@ -118,7 +118,7 @@ export default { $('.postCommentsLoader .lds-ring') .attr('style','width:100%') .addClass('pt-4 font-weight-bold text-muted') - .text('An error occured, cannot fetch comments. Please try again later.'); + .text('An error occurred, cannot fetch comments. Please try again later.'); break; } } diff --git a/resources/assets/js/components/PostComponent.vue b/resources/assets/js/components/PostComponent.vue index c3b9f6258..fcad803ef 100644 --- a/resources/assets/js/components/PostComponent.vue +++ b/resources/assets/js/components/PostComponent.vue @@ -56,11 +56,11 @@
-
+
- +
@@ -156,6 +156,7 @@ +
@@ -164,10 +165,10 @@
-
@@ -195,10 +196,10 @@
-
@@ -281,7 +282,7 @@ export default { $('head title').text(title); } }, - + methods: { authCheck() { let authed = $('body').hasClass('loggedIn'); @@ -339,7 +340,7 @@ export default { $('.postPresenterContainer').removeClass('d-none'); }).catch(error => { if(!error.response) { - $('.postPresenterLoader .lds-ring').attr('style','width:100%').addClass('pt-4 font-weight-bold text-muted').text('An error occured, cannot fetch media. Please try again later.'); + $('.postPresenterLoader .lds-ring').attr('style','width:100%').addClass('pt-4 font-weight-bold text-muted').text('An error occurred, cannot fetch media. Please try again later.'); } else { switch(error.response.status) { case 401: @@ -350,7 +351,7 @@ export default { break; default: - $('.postPresenterLoader .lds-ring').attr('style','width:100%').addClass('pt-4 font-weight-bold text-muted').text('An error occured, cannot fetch media. Please try again later.'); + $('.postPresenterLoader .lds-ring').attr('style','width:100%').addClass('pt-4 font-weight-bold text-muted').text('An error occurred, cannot fetch media. Please try again later.'); break; } } @@ -510,4 +511,4 @@ export default { } } } - \ No newline at end of file + diff --git a/resources/assets/js/components/Timeline.vue b/resources/assets/js/components/Timeline.vue index baf48c9c8..e265ae6fc 100644 --- a/resources/assets/js/components/Timeline.vue +++ b/resources/assets/js/components/Timeline.vue @@ -156,22 +156,22 @@ @@ -211,6 +211,9 @@ .cursor-pointer { cursor: pointer; } + .word-break { + word-break: break-all; + } @endpush diff --git a/resources/views/settings/remove/permanent.blade.php b/resources/views/settings/remove/permanent.blade.php index b95aa2894..a805ce668 100644 --- a/resources/views/settings/remove/permanent.blade.php +++ b/resources/views/settings/remove/permanent.blade.php @@ -16,7 +16,7 @@

When you press the button below, your photos, comments, likes, friendships and all other data will be removed permanently and will not be recoverable. If you decide to create another Pixelfed account in the future, you cannot sign up with the same username again on this instance.

- Warning: Some remote servers may contain your public data (statuses, avatars, ect) and will not be deleted until federation support is launched. + Warning: Some remote servers may contain your public data (statuses, avatars, etc) and will not be deleted until federation support is launched.

diff --git a/resources/views/settings/security/2fa/recovery-codes.blade.php b/resources/views/settings/security/2fa/recovery-codes.blade.php index 47f37af29..9b6c61e4a 100644 --- a/resources/views/settings/security/2fa/recovery-codes.blade.php +++ b/resources/views/settings/security/2fa/recovery-codes.blade.php @@ -7,16 +7,26 @@


- -

- Each code can only be used once. -

- -

- + @if(count($codes) > 0) +

+ Each code can only be used once. +

+ + @else +
+

You are out of recovery codes

+

Generate more recovery codes and store them in a safe place.

+

+

+ @csrf + +
+

+
+ @endif @endsection \ No newline at end of file diff --git a/resources/views/site/help/getting-started.blade.php b/resources/views/site/help/getting-started.blade.php index 10adae72f..a764433f1 100644 --- a/resources/views/site/help/getting-started.blade.php +++ b/resources/views/site/help/getting-started.blade.php @@ -64,7 +64,7 @@

diff --git a/resources/views/status/compose.blade.php b/resources/views/status/compose.blade.php index 8b5251a2f..691a97a3e 100644 --- a/resources/views/status/compose.blade.php +++ b/resources/views/status/compose.blade.php @@ -354,7 +354,7 @@ $(document).on('change', '.file-input', function(e) { el.remove(); } }).catch(function(e) { - swal('Oops, something went wrong!', 'An unexpected error occured.', 'error'); + swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error'); }); io.value = null; }); @@ -478,7 +478,7 @@ $(document).on('click', '#create', function(e) { let data = res.data; window.location.href = data; }).catch(err => { - swal('Oops, something went wrong!', 'An unexpected error occured.', 'error'); + swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error'); }); }) diff --git a/resources/views/status/edit.blade.php b/resources/views/status/edit.blade.php index ffb23cb37..d69843552 100644 --- a/resources/views/status/edit.blade.php +++ b/resources/views/status/edit.blade.php @@ -83,7 +83,7 @@ }).then((res) => { swal('Success!', 'You have successfully updated your post', 'success'); }).catch((err) => { - swal('Something went wrong', 'An error occured, please try again later', 'error'); + swal('Something went wrong', 'An error occurred, please try again later', 'error'); }); }); diff --git a/routes/web.php b/routes/web.php index 8aebe6e90..f74f894e6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -92,7 +92,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::group(['prefix' => 'report'], function () { Route::get('/', 'ReportController@showForm')->name('report.form'); - Route::post('/', 'ReportController@formStore')->middleware('throttle:100,1440'); + Route::post('/', 'ReportController@formStore')->middleware('throttle:10,5'); Route::get('not-interested', 'ReportController@notInterestedForm')->name('report.not-interested'); Route::get('spam', 'ReportController@spamForm')->name('report.spam'); Route::get('spam/comment', 'ReportController@spamCommentForm')->name('report.spam.comment'); @@ -120,7 +120,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact ->name('settings'); Route::post('home', 'SettingsController@homeUpdate')->middleware('throttle:250,1440'); Route::get('avatar', 'SettingsController@avatar')->name('settings.avatar'); - Route::post('avatar', 'AvatarController@store')->middleware('throttle:50,1440'); + Route::post('avatar', 'AvatarController@store'); Route::get('password', 'SettingsController@password')->name('settings.password')->middleware('dangerzone'); Route::post('password', 'SettingsController@passwordUpdate')->middleware(['throttle:2,1440','dangerzone']); Route::get('email', 'SettingsController@email')->name('settings.email'); @@ -166,6 +166,10 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact '2fa/recovery-codes', 'SettingsController@securityTwoFactorRecoveryCodes' )->name('settings.security.2fa.recovery'); + Route::post( + '2fa/recovery-codes', + 'SettingsController@securityTwoFactorRecoveryCodesRegenerate' + ); }); Route::get('applications', 'SettingsController@applications')->name('settings.applications'); diff --git a/tests/Unit/ActivityPub/AcceptVerbTest.php b/tests/Unit/ActivityPub/AcceptVerbTest.php new file mode 100644 index 000000000..c949624db --- /dev/null +++ b/tests/Unit/ActivityPub/AcceptVerbTest.php @@ -0,0 +1,55 @@ +validAccept = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => 'https://example.org/og/b3e4a40b-0b26-4c5a-9079-094bd633fab7', + 'type' => 'Accept', + 'actor' => 'https://example.org/u/alice', + 'object' => [ + 'id' => 'https://example.net/u/bob#follows/bb27f601-ddb9-4567-8f16-023d90605ca9', + 'type' => 'Follow', + 'actor' => 'https://example.net/u/bob', + 'object' => 'https://example.org/u/alice' + ] + ]; + $this->invalidAccept = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => 'https://example.org/og/b3e4a40b-0b26-4c5a-9079-094bd633fab7', + 'type' => 'Accept2', + 'actor' => 'https://example.org/u/alice', + 'object' => [ + 'id' => 'https://example.net/u/bob#follows/bb27f601-ddb9-4567-8f16-023d90605ca9', + 'type' => 'Follow', + 'actor' => 'https://example.net/u/bob', + 'object' => 'https://example.org/u/alice' + ] + ]; + } + + /** @test */ + public function basic_accept() + { + $this->assertTrue(Accept::validate($this->validAccept)); + } + + /** @test */ + public function invalid_accept() + { + $this->assertFalse(Accept::validate($this->invalidAccept)); + } +}