diff --git a/app/Console/Commands/CatchUnoptimizedMedia.php b/app/Console/Commands/CatchUnoptimizedMedia.php index 6b3c3547d97fe238feac9932cc5bb4774f59457e..9038c12fba902f9931c9469071c5dd2938703d4c 100644 --- a/app/Console/Commands/CatchUnoptimizedMedia.php +++ b/app/Console/Commands/CatchUnoptimizedMedia.php @@ -42,9 +42,11 @@ class CatchUnoptimizedMedia extends Command { DB::transaction(function() { Media::whereNull('processed_at') + ->where('skip_optimize', '!=', true) ->whereNull('remote_url') ->whereNotNull('status_id') ->whereNotNull('media_path') + ->where('created_at', '>', now()->subHours(1)) ->whereIn('mime', [ 'image/jpeg', 'image/png', diff --git a/app/Console/Commands/FixSoftDeletedProfile.php b/app/Console/Commands/FixSoftDeletedProfile.php deleted file mode 100644 index 3fe90ff3cec8b199b4a15acfdbb04ca4609576c4..0000000000000000000000000000000000000000 --- a/app/Console/Commands/FixSoftDeletedProfile.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php - -namespace App\Console\Commands; - -use Illuminate\Console\Command; -use App\Profile; -use App\User; - -class FixSoftDeletedProfile extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'fix:sdprofile'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Command description'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return int - */ - public function handle() - { - $profiles = Profile::whereNull('domain') - ->withTrashed() - ->where('deleted_at', '>', now()->subDays(14)) - ->whereNull('status') - ->pluck('username'); - - if($profiles->count() == 0) { - return 0; - } - - foreach($profiles as $p) { - if(User::whereUsername($p)->first()->status == null) { - $pro = Profile::withTrashed()->whereUsername($p)->firstOrFail(); - $pro->deleted_at = null; - $pro->save(); - } - } - } -} diff --git a/app/Console/Commands/GenerateInstanceActor.php b/app/Console/Commands/GenerateInstanceActor.php new file mode 100644 index 0000000000000000000000000000000000000000..c70b13a12a5b0509739532f47e7b09f20c4e2269 --- /dev/null +++ b/app/Console/Commands/GenerateInstanceActor.php @@ -0,0 +1,75 @@ +<?php + +namespace App\Console\Commands; + +use Illuminate\Console\Command; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\DB; +use App\Models\InstanceActor; +use Cache; + +class GenerateInstanceActor extends Command +{ + protected $signature = 'instance:actor'; + protected $description = 'Generate instance actor'; + + public function __construct() + { + parent::__construct(); + } + + public function handle() + { + if(Schema::hasTable('instance_actors') == false) { + $this->line(' '); + $this->error('Missing instance_actors table.'); + $this->info('Run "php artisan migrate" and try again.'); + $this->line(' '); + exit; + } + + if(InstanceActor::exists()) { + $this->line(' '); + $this->error('Instance actor already exists!'); + $this->line(' '); + $actor = InstanceActor::whereNotNull('public_key') + ->whereNotNull('private_key') + ->firstOrFail(); + Cache::rememberForever(InstanceActor::PKI_PUBLIC, function() use($actor) { + return $actor->public_key; + }); + + Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() use($actor) { + return $actor->private_key; + }); + exit; + } + + $pkiConfig = [ + 'digest_alg' => 'sha512', + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]; + $pki = openssl_pkey_new($pkiConfig); + openssl_pkey_export($pki, $pki_private); + $pki_public = openssl_pkey_get_details($pki); + $pki_public = $pki_public['key']; + + $actor = new InstanceActor(); + $actor->public_key = $pki_public; + $actor->private_key = $pki_private; + $actor->save(); + + Cache::rememberForever(InstanceActor::PKI_PUBLIC, function() use($actor) { + return $actor->public_key; + }); + + Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() use($actor) { + return $actor->private_key; + }); + + $this->info('Instance actor succesfully generated. You do not need to run this command again.'); + + return 0; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index d5e1d47a46e0330ab18f914adf29ddc15ae1e728..0dd65888ab3d5a5bdd13afdb5fb5d3c703f361c1 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -25,13 +25,10 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('media:optimize') - ->hourly(); - $schedule->command('media:gc') - ->hourly(); + $schedule->command('media:optimize')->hourly(); + $schedule->command('media:gc')->hourly(); $schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('story:gc')->everyFiveMinutes(); - $schedule->command('fix:sdprofile')->everyFiveMinutes(); $schedule->command('gc:failedjobs')->dailyAt(3); $schedule->command('gc:passwordreset')->dailyAt('09:41'); } diff --git a/app/Http/Controllers/InstanceActorController.php b/app/Http/Controllers/InstanceActorController.php new file mode 100644 index 0000000000000000000000000000000000000000..136a2ede5dcbac98fe37db4f74159d515712ec42 --- /dev/null +++ b/app/Http/Controllers/InstanceActorController.php @@ -0,0 +1,37 @@ +<?php + +namespace App\Http\Controllers; + +use Illuminate\Http\Request; +use App\Models\InstanceActor; +use Cache; + +class InstanceActorController extends Controller +{ + public function profile() + { + $res = Cache::rememberForever(InstanceActor::PROFILE_KEY, function() { + $res = (new InstanceActor())->first()->getActor(); + return json_encode($res); + }); + return response($res)->header('Content-Type', 'application/json'); + } + + public function inbox() + { + return; + } + + public function outbox() + { + $res = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => config('app.url') . '/i/actor/outbox', + 'type' => 'OrderedCollection', + 'totalItems' => 0, + 'first' => config('app.url') . '/i/actor/outbox?page=true', + 'last' => config('app.url') . '/i/actor/outbox?min_id=0page=true' + ]; + return response()->json($res); + } +} diff --git a/app/Models/InstanceActor.php b/app/Models/InstanceActor.php new file mode 100644 index 0000000000000000000000000000000000000000..fad1fc86bfd2883be72d3ed5bd1485f66aff70a7 --- /dev/null +++ b/app/Models/InstanceActor.php @@ -0,0 +1,41 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; + +class InstanceActor extends Model +{ + use HasFactory; + + const PROFILE_BASE = '/i/actor'; + const KEY_ID = '/i/actor#main-key'; + const PROFILE_KEY = 'federation:_v2:instance:actor:profile'; + const PKI_PUBLIC = 'federation:_v1:instance:actor:profile:pki_public'; + const PKI_PRIVATE = 'federation:_v1:instance:actor:profile:pki_private'; + + public function permalink($suffix = '') + { + return url(self::PROFILE_BASE . $suffix); + } + + public function getActor() + { + return [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $this->permalink(), + 'type' => 'Application', + 'inbox' => $this->permalink('/inbox'), + 'outbox' => $this->permalink('/outbox'), + 'preferredUsername' => config('pixelfed.domain.app'), + 'publicKey' => [ + 'id' => $this->permalink('#main-key'), + 'owner' => $this->permalink(), + 'publicKeyPem' => $this->public_key + ], + 'manuallyApprovesFollowers' => true, + 'url' => route('help.instance-actor') + ]; + } +} diff --git a/app/Util/ActivityPub/HttpSignature.php b/app/Util/ActivityPub/HttpSignature.php index d6ed0040ba2eeaf13ec1e7632396a2be091e546a..516979f5cba35545fbd05a2e7633348320e1b388 100644 --- a/app/Util/ActivityPub/HttpSignature.php +++ b/app/Util/ActivityPub/HttpSignature.php @@ -2,7 +2,8 @@ namespace App\Util\ActivityPub; -use Log; +use Cache, Log; +use App\Models\InstanceActor; use App\Profile; use \DateTime; @@ -32,6 +33,29 @@ class HttpSignature { return self::_headersToCurlArray($headers); } + public static function instanceActorSign($url, $body = false, $addlHeaders = []) + { + $keyId = config('app.url') . '/i/actor#main-key'; + $privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() { + return InstanceActor::first()->private_key; + }); + if($body) { + $digest = self::_digest($body); + } + $headers = self::_headersToSign($url, $body ? $digest : false); + $headers = array_merge($headers, $addlHeaders); + $stringToSign = self::_headersToSigningString($headers); + $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers))); + $key = openssl_pkey_get_private($privateKey); + openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256); + $signature = base64_encode($signature); + $signatureHeader = 'keyId="'.$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 = []; diff --git a/app/Util/Lexer/RestrictedNames.php b/app/Util/Lexer/RestrictedNames.php index e104b31d2ad26cd96cfb14879c22f8808ad3f828..09af01bfaca695bc87f8d3541e71a4b262117d00 100644 --- a/app/Util/Lexer/RestrictedNames.php +++ b/app/Util/Lexer/RestrictedNames.php @@ -98,6 +98,8 @@ class RestrictedNames 'aboutus', 'about-us', 'abuse', + 'actor', + 'actors', 'account', 'admins', 'api', @@ -179,6 +181,7 @@ class RestrictedNames 'help-center_', 'help_center-', 'i', + 'instance', 'inbox', 'img', 'imgs', @@ -208,6 +211,17 @@ class RestrictedNames 'media', 'menu', 'music', + 'my2020', + 'my2021', + 'my2022', + 'my2023', + 'my2024', + 'my2025', + 'my2026', + 'my2027', + 'my2028', + 'my2029', + 'my2030', 'n', 'news', 'new', diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php index b59b610f0f58965d39f3d1fce2bafc8c849cd980..88c211ca98657e7a7c7c8a379bb0fd1b26a9bce2 100644 --- a/app/Util/Site/Config.php +++ b/app/Util/Site/Config.php @@ -8,7 +8,7 @@ use Illuminate\Support\Str; class Config { public static function get() { - return Cache::remember('api:site:configuration:_v0', now()->addHours(30), function() { + return Cache::remember('api:site:configuration:_v0.1', now()->addHours(30), function() { return [ 'open_registration' => config('pixelfed.open_registration'), 'uploader' => [ @@ -34,7 +34,8 @@ class Config { 'ab' => [ 'lc' => config('exp.lc'), 'rec' => config('exp.rec'), - 'loops' => config('exp.loops') + 'loops' => config('exp.loops'), + 'top' => config('exp.top') ], 'site' => [ diff --git a/config/exp.php b/config/exp.php index 440997614d213a0e3760c5d617ccc0b69b816dd6..74e9a5e49ed88ab5884ab518e5419b61b0921599 100644 --- a/config/exp.php +++ b/config/exp.php @@ -3,6 +3,7 @@ return [ 'lc' => env('EXP_LC', false), - 'rec' => env('EXP_REC', false), - 'loops' => env('EXP_LOOPS', false), + 'rec' => false, + 'loops' => false, + 'top' => env('EXP_TOP', false), ]; diff --git a/database/migrations/2021_01_15_050602_create_instance_actors_table.php b/database/migrations/2021_01_15_050602_create_instance_actors_table.php new file mode 100644 index 0000000000000000000000000000000000000000..2f06cd3fbecb2e6570e67d0c39c3a556cbdd234b --- /dev/null +++ b/database/migrations/2021_01_15_050602_create_instance_actors_table.php @@ -0,0 +1,33 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class CreateInstanceActorsTable extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::create('instance_actors', function (Blueprint $table) { + $table->id(); + $table->text('private_key')->nullable(); + $table->text('public_key')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('instance_actors'); + } +}