From e512e0d6cdfc074bfd8cd7f580fd70638ed011da Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 14 Jul 2024 11:12:43 +0200 Subject: [PATCH] port_patches --- patches/0001-remove-IP-logging.patch | 1067 +---------------- .../0002-hardcode-discovery-settings.patch | 4 +- .../0003-point-to-modified-sourcecode.patch | 4 +- patches/0004-disable-beagle-service.patch | 4 +- patches/0005-allow-30-char-usernames.patch | 24 +- patches/0006-Link-legal-notice.patch | 4 +- 6 files changed, 44 insertions(+), 1063 deletions(-) diff --git a/patches/0001-remove-IP-logging.patch b/patches/0001-remove-IP-logging.patch index 83016cf..e0ed1d0 100644 --- a/patches/0001-remove-IP-logging.patch +++ b/patches/0001-remove-IP-logging.patch @@ -1,25 +1,23 @@ -From 5cceb503321bd8a5eb8fbed111c6fb708596836b Mon Sep 17 00:00:00 2001 +From 39b4d1f323e3bed9e2381e04b2dad8ac691f69dc Mon Sep 17 00:00:00 2001 From: chris -Date: Tue, 2 Jul 2024 12:26:00 +0200 -Subject: [PATCH] remove IP logging +Date: Sun, 14 Jul 2024 11:06:12 +0200 +Subject: [PATCH 1/6] remove IP logging Replace unneeded logging of IPs and User-Agent strings with hashed data. --- - .gitattributes | 12 - - .../Controllers/Api/ApiV1Dot1Controller.php | 12 +- - .../Api/ApiV1Dot1Controller.php.orig | 1011 +++++++++++++++++ - app/Http/Controllers/Auth/LoginController.php | 4 +- - .../Controllers/Auth/RegisterController.php | 2 +- - .../Controllers/CuratedRegisterController.php | 2 +- - app/Http/Controllers/RemoteAuthController.php | 4 +- - app/Http/Controllers/SeasonalController.php | 4 +- - .../Controllers/Settings/HomeSettings.php | 8 +- - .../Controllers/UserEmailForgotController.php | 4 +- - app/Listeners/AuthLogin.php | 4 +- - app/Listeners/LogFailedLogin.php | 4 +- - 12 files changed, 1035 insertions(+), 36 deletions(-) + .gitattributes | 12 ------------ + app/Http/Controllers/Api/ApiV1Dot1Controller.php | 12 ++++++------ + app/Http/Controllers/Auth/LoginController.php | 4 ++-- + app/Http/Controllers/Auth/RegisterController.php | 2 +- + app/Http/Controllers/CuratedRegisterController.php | 2 +- + app/Http/Controllers/RemoteAuthController.php | 4 ++-- + app/Http/Controllers/SeasonalController.php | 4 ++-- + app/Http/Controllers/Settings/HomeSettings.php | 8 ++++---- + app/Http/Controllers/UserEmailForgotController.php | 4 ++-- + app/Listeners/AuthLogin.php | 4 ++-- + app/Listeners/LogFailedLogin.php | 4 ++-- + 11 files changed, 24 insertions(+), 36 deletions(-) delete mode 100644 .gitattributes - create mode 100644 app/Http/Controllers/Api/ApiV1Dot1Controller.php.orig diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 @@ -90,1023 +88,6 @@ index 9a47bb15..0bd5b482 100644 abort_if(! $rl, 429, 'Too many requests'); $request->validate([ -diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php.orig b/app/Http/Controllers/Api/ApiV1Dot1Controller.php.orig -new file mode 100644 -index 00000000..9a47bb15 ---- /dev/null -+++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php.orig -@@ -0,0 +1,1011 @@ -+fractal = new Fractal\Manager(); -+ $this->fractal->setSerializer(new ArraySerializer()); -+ } -+ -+ public function json($res, $code = 200, $headers = []) -+ { -+ return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES); -+ } -+ -+ public function error($msg, $code = 400, $extra = [], $headers = []) -+ { -+ $res = [ -+ 'msg' => $msg, -+ 'code' => $code, -+ ]; -+ -+ return response()->json(array_merge($res, $extra), $code, $headers, JSON_UNESCAPED_SLASHES); -+ } -+ -+ public function report(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('write'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $report_type = $request->input('report_type'); -+ $object_id = $request->input('object_id'); -+ $object_type = $request->input('object_type'); -+ -+ $types = [ -+ 'spam', -+ 'sensitive', -+ 'abusive', -+ 'underage', -+ 'violence', -+ 'copyright', -+ 'impersonation', -+ 'scam', -+ 'terrorism', -+ ]; -+ -+ if (! $report_type || ! $object_id || ! $object_type) { -+ return $this->error('Invalid or missing parameters', 400, ['error_code' => 'ERROR_INVALID_PARAMS']); -+ } -+ -+ if (! in_array($report_type, $types)) { -+ return $this->error('Invalid report type', 400, ['error_code' => 'ERROR_TYPE_INVALID']); -+ } -+ -+ if ($object_type === 'user' && $object_id == $user->profile_id) { -+ return $this->error('Cannot self report', 400, ['error_code' => 'ERROR_NO_SELF_REPORTS']); -+ } -+ -+ $rpid = null; -+ -+ switch ($object_type) { -+ case 'post': -+ $object = Status::find($object_id); -+ if (! $object) { -+ return $this->error('Invalid object id', 400, ['error_code' => 'ERROR_INVALID_OBJECT_ID']); -+ } -+ $object_type = 'App\Status'; -+ $exists = Report::whereUserId($user->id) -+ ->whereObjectId($object->id) -+ ->whereObjectType('App\Status') -+ ->count(); -+ -+ $rpid = $object->profile_id; -+ break; -+ -+ case 'user': -+ $object = Profile::find($object_id); -+ if (! $object) { -+ return $this->error('Invalid object id', 400, ['error_code' => 'ERROR_INVALID_OBJECT_ID']); -+ } -+ $object_type = 'App\Profile'; -+ $exists = Report::whereUserId($user->id) -+ ->whereObjectId($object->id) -+ ->whereObjectType('App\Profile') -+ ->count(); -+ $rpid = $object->id; -+ break; -+ -+ default: -+ return $this->error('Invalid report type', 400, ['error_code' => 'ERROR_REPORT_OBJECT_TYPE_INVALID']); -+ break; -+ } -+ -+ if ($exists !== 0) { -+ return $this->error('Duplicate report', 400, ['error_code' => 'ERROR_REPORT_DUPLICATE']); -+ } -+ -+ if ($object->profile_id == $user->profile_id) { -+ return $this->error('Cannot self report', 400, ['error_code' => 'ERROR_NO_SELF_REPORTS']); -+ } -+ -+ $report = new Report; -+ $report->profile_id = $user->profile_id; -+ $report->user_id = $user->id; -+ $report->object_id = $object->id; -+ $report->object_type = $object_type; -+ $report->reported_profile_id = $rpid; -+ $report->type = $report_type; -+ $report->save(); -+ -+ if (config('instance.reports.email.enabled')) { -+ ReportNotifyAdminViaEmail::dispatch($report)->onQueue('default'); -+ } -+ -+ $res = [ -+ 'msg' => 'Successfully sent report', -+ 'code' => 200, -+ ]; -+ -+ return $this->json($res); -+ } -+ -+ /** -+ * DELETE /api/v1.1/accounts/avatar -+ * -+ * @return \App\Transformer\Api\AccountTransformer -+ */ -+ public function deleteAvatar(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('write'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $avatar = $user->profile->avatar; -+ -+ if ($avatar->media_path == 'public/avatars/default.png' || -+ $avatar->media_path == 'public/avatars/default.jpg' -+ ) { -+ return AccountService::get($user->profile_id); -+ } -+ -+ if (is_file(storage_path('app/'.$avatar->media_path))) { -+ @unlink(storage_path('app/'.$avatar->media_path)); -+ } -+ -+ $avatar->media_path = 'public/avatars/default.jpg'; -+ $avatar->change_count = $avatar->change_count + 1; -+ $avatar->save(); -+ -+ Cache::forget('avatar:'.$user->profile_id); -+ Cache::forget("avatar:{$user->profile_id}"); -+ Cache::forget('user:account:id:'.$user->id); -+ AccountService::del($user->profile_id); -+ -+ return AccountService::get($user->profile_id); -+ } -+ -+ /** -+ * GET /api/v1.1/accounts/{id}/posts -+ * -+ * @return \App\Transformer\Api\StatusTransformer -+ */ -+ public function accountPosts(Request $request, $id) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $account = AccountService::get($id); -+ -+ if (! $account || $account['username'] !== $request->input('username')) { -+ return $this->json([]); -+ } -+ -+ $posts = ProfileStatusService::get($id); -+ -+ if (! $posts) { -+ return $this->json([]); -+ } -+ -+ $res = collect($posts) -+ ->map(function ($id) { -+ return StatusService::get($id); -+ }) -+ ->filter(function ($post) { -+ return $post && isset($post['account']); -+ }) -+ ->toArray(); -+ -+ return $this->json($res); -+ } -+ -+ /** -+ * POST /api/v1.1/accounts/change-password -+ * -+ * @return \App\Transformer\Api\AccountTransformer -+ */ -+ public function accountChangePassword(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('write'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $this->validate($request, [ -+ 'current_password' => 'bail|required|current_password', -+ 'new_password' => 'required|min:'.config('pixelfed.min_password_length', 8), -+ 'confirm_password' => 'required|same:new_password', -+ ], [ -+ 'current_password' => 'The password you entered is incorrect', -+ ]); -+ -+ $user->password = bcrypt($request->input('new_password')); -+ $user->save(); -+ -+ $log = new AccountLog; -+ $log->user_id = $user->id; -+ $log->item_id = $user->id; -+ $log->item_type = 'App\User'; -+ $log->action = 'account.edit.password'; -+ $log->message = 'Password changed'; -+ $log->link = null; -+ $log->ip_address = $request->ip(); -+ $log->user_agent = $request->userAgent(); -+ $log->save(); -+ -+ Mail::to($request->user())->send(new PasswordChange($user)); -+ -+ return $this->json(AccountService::get($user->profile_id)); -+ } -+ -+ /** -+ * GET /api/v1.1/accounts/login-activity -+ * -+ * @return array -+ */ -+ public function accountLoginActivity(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ $agent = new Agent(); -+ $currentIp = $request->ip(); -+ -+ $activity = AccountLog::whereUserId($user->id) -+ ->whereAction('auth.login') -+ ->orderBy('created_at', 'desc') -+ ->groupBy('ip_address') -+ ->limit(10) -+ ->get() -+ ->map(function ($item) use ($agent, $currentIp) { -+ $agent->setUserAgent($item->user_agent); -+ -+ return [ -+ 'id' => $item->id, -+ 'action' => $item->action, -+ 'ip' => $item->ip_address, -+ 'ip_current' => $item->ip_address === $currentIp, -+ 'is_mobile' => $agent->isMobile(), -+ 'device' => $agent->device(), -+ 'browser' => $agent->browser(), -+ 'platform' => $agent->platform(), -+ 'created_at' => $item->created_at->format('c'), -+ ]; -+ }); -+ -+ return $this->json($activity); -+ } -+ -+ /** -+ * GET /api/v1.1/accounts/two-factor -+ * -+ * @return array -+ */ -+ public function accountTwoFactor(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $res = [ -+ 'active' => (bool) $user->{'2fa_enabled'}, -+ 'setup_at' => $user->{'2fa_setup_at'}, -+ ]; -+ -+ return $this->json($res); -+ } -+ -+ /** -+ * GET /api/v1.1/accounts/emails-from-pixelfed -+ * -+ * @return array -+ */ -+ public function accountEmailsFromPixelfed(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ $from = config('mail.from.address'); -+ -+ $emailVerifications = EmailVerification::whereUserId($user->id) -+ ->orderByDesc('id') -+ ->where('created_at', '>', now()->subDays(14)) -+ ->limit(10) -+ ->get() -+ ->map(function ($mail) use ($user, $from) { -+ return [ -+ 'type' => 'Email Verification', -+ 'subject' => 'Confirm Email', -+ 'to_address' => $user->email, -+ 'from_address' => $from, -+ 'created_at' => str_replace('@', 'at', $mail->created_at->format('M j, Y @ g:i:s A')), -+ ]; -+ }) -+ ->toArray(); -+ -+ $passwordResets = DB::table('password_resets') -+ ->whereEmail($user->email) -+ ->where('created_at', '>', now()->subDays(14)) -+ ->orderByDesc('created_at') -+ ->limit(10) -+ ->get() -+ ->map(function ($mail) use ($user, $from) { -+ return [ -+ 'type' => 'Password Reset', -+ 'subject' => 'Reset Password Notification', -+ 'to_address' => $user->email, -+ 'from_address' => $from, -+ 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')), -+ ]; -+ }) -+ ->toArray(); -+ -+ $passwordChanges = AccountLog::whereUserId($user->id) -+ ->whereAction('account.edit.password') -+ ->where('created_at', '>', now()->subDays(14)) -+ ->orderByDesc('created_at') -+ ->limit(10) -+ ->get() -+ ->map(function ($mail) use ($user, $from) { -+ return [ -+ 'type' => 'Password Change', -+ 'subject' => 'Password Change', -+ 'to_address' => $user->email, -+ 'from_address' => $from, -+ 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')), -+ ]; -+ }) -+ ->toArray(); -+ -+ $res = collect([]) -+ ->merge($emailVerifications) -+ ->merge($passwordResets) -+ ->merge($passwordChanges) -+ ->sortByDesc('created_at') -+ ->values(); -+ -+ return $this->json($res); -+ } -+ -+ /** -+ * GET /api/v1.1/accounts/apps-and-applications -+ * -+ * @return array -+ */ -+ public function accountApps(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ $user = $request->user(); -+ abort_if($user->status != null, 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $res = $user->tokens->sortByDesc('created_at')->take(10)->map(function ($token, $key) use ($request) { -+ return [ -+ 'id' => $token->id, -+ 'current_session' => $request->user()->token()->id == $token->id, -+ 'name' => $token->client->name, -+ 'scopes' => $token->scopes, -+ 'revoked' => $token->revoked, -+ 'created_at' => str_replace('@', 'at', now()->parse($token->created_at)->format('M j, Y @ g:i:s A')), -+ 'expires_at' => str_replace('@', 'at', now()->parse($token->expires_at)->format('M j, Y @ g:i:s A')), -+ ]; -+ }); -+ -+ return $this->json($res); -+ } -+ -+ public function inAppRegistrationPreFlightCheck(Request $request) -+ { -+ return [ -+ 'open' => (bool) config_cache('pixelfed.open_registration'), -+ 'iara' => (bool) config_cache('pixelfed.allow_app_registration'), -+ ]; -+ } -+ -+ public function inAppRegistration(Request $request) -+ { -+ abort_if($request->user(), 404); -+ abort_unless((bool) config_cache('pixelfed.open_registration'), 404); -+ abort_unless((bool) config_cache('pixelfed.allow_app_registration'), 404); -+ abort_unless($request->hasHeader('X-PIXELFED-APP'), 403); -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $rl = RateLimiter::attempt('pf:apiv1.1:iar:'.$request->ip(), config('pixelfed.app_registration_rate_limit_attempts', 3), function () {}, config('pixelfed.app_registration_rate_limit_decay', 1800)); -+ abort_if(! $rl, 400, 'Too many requests'); -+ -+ $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', -+ ]); -+ -+ $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(40); -+ $user->save(); -+ -+ $rtoken = Str::random(64); -+ -+ $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(); -+ -+ $params = http_build_query([ -+ 'ut' => $user->app_register_token, -+ 'rt' => $rtoken, -+ 'ea' => base64_encode($user->email), -+ ]); -+ $appUrl = url('/api/v1.1/auth/iarer?'.$params); -+ -+ Mail::to($user->email)->send(new ConfirmAppEmail($verify, $appUrl)); -+ -+ return response()->json([ -+ 'success' => true, -+ ]); -+ } -+ -+ public function inAppRegistrationEmailRedirect(Request $request) -+ { -+ $this->validate($request, [ -+ 'ut' => 'required', -+ 'rt' => 'required', -+ 'ea' => 'required', -+ ]); -+ $ut = $request->input('ut'); -+ $rt = $request->input('rt'); -+ $ea = $request->input('ea'); -+ $params = http_build_query([ -+ 'ut' => $ut, -+ 'rt' => $rt, -+ 'domain' => config('pixelfed.domain.app'), -+ 'ea' => $ea, -+ ]); -+ $url = 'pixelfed://confirm-account/'.$ut.'?'.$params; -+ -+ return redirect()->away($url); -+ } -+ -+ public function inAppRegistrationConfirm(Request $request) -+ { -+ abort_if($request->user(), 404); -+ abort_unless((bool) config_cache('pixelfed.open_registration'), 404); -+ abort_unless((bool) config_cache('pixelfed.allow_app_registration'), 404); -+ abort_unless($request->hasHeader('X-PIXELFED-APP'), 403); -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function () {}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800)); -+ abort_if(! $rl, 429, 'Too many requests'); -+ -+ $request->validate([ -+ '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); -+ } -+ -+ if ($verify->created_at->lt(now()->subHours(24))) { -+ $verify->delete(); -+ -+ 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(); -+ -+ $token = $user->createToken('Pixelfed', ['read', 'write', 'follow', 'admin:read', 'admin:write', 'push']); -+ -+ return response()->json([ -+ 'access_token' => $token->accessToken, -+ ]); -+ } -+ -+ public function archive(Request $request, $id) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('write'), 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $status = Status::whereNull('in_reply_to_id') -+ ->whereNull('reblog_of_id') -+ ->whereProfileId($request->user()->profile_id) -+ ->findOrFail($id); -+ -+ if ($status->scope === 'archived') { -+ return [200]; -+ } -+ -+ $archive = new StatusArchived; -+ $archive->status_id = $status->id; -+ $archive->profile_id = $status->profile_id; -+ $archive->original_scope = $status->scope; -+ $archive->save(); -+ -+ $status->scope = 'archived'; -+ $status->visibility = 'draft'; -+ $status->save(); -+ StatusService::del($status->id, true); -+ AccountService::syncPostCount($status->profile_id); -+ -+ return [200]; -+ } -+ -+ public function unarchive(Request $request, $id) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('write'), 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $status = Status::whereNull('in_reply_to_id') -+ ->whereNull('reblog_of_id') -+ ->whereProfileId($request->user()->profile_id) -+ ->findOrFail($id); -+ -+ if ($status->scope !== 'archived') { -+ return [200]; -+ } -+ -+ $archive = StatusArchived::whereStatusId($status->id) -+ ->whereProfileId($status->profile_id) -+ ->firstOrFail(); -+ -+ $status->scope = $archive->original_scope; -+ $status->visibility = $archive->original_scope; -+ $status->save(); -+ $archive->delete(); -+ StatusService::del($status->id, true); -+ AccountService::syncPostCount($status->profile_id); -+ -+ return [200]; -+ } -+ -+ public function archivedPosts(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $statuses = Status::whereProfileId($request->user()->profile_id) -+ ->whereScope('archived') -+ ->orderByDesc('id') -+ ->cursorPaginate(10); -+ -+ return StatusStateless::collection($statuses); -+ } -+ -+ public function placesById(Request $request, $id, $slug) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $place = Place::whereSlug($slug)->findOrFail($id); -+ -+ $posts = Cache::remember('pf-api:v1.1:places-by-id:'.$place->id, 3600, function () use ($place) { -+ return Status::wherePlaceId($place->id) -+ ->whereNull('uri') -+ ->whereScope('public') -+ ->orderByDesc('created_at') -+ ->limit(60) -+ ->pluck('id'); -+ }); -+ -+ $posts = $posts->map(function ($id) { -+ return StatusService::get($id); -+ }) -+ ->filter() -+ ->values(); -+ -+ return [ -+ 'place' => [ -+ 'id' => $place->id, -+ 'name' => $place->name, -+ 'slug' => $place->slug, -+ 'country' => $place->country, -+ 'lat' => $place->lat, -+ 'long' => $place->long, -+ ], -+ 'posts' => $posts]; -+ } -+ -+ public function moderatePost(Request $request, $id) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_if($request->user()->is_admin != true, 403); -+ abort_unless($request->user()->tokenCan('admin:write'), 403); -+ -+ if (config('pixelfed.bouncer.cloud_ips.ban_signups')) { -+ abort_if(BouncerService::checkIp($request->ip()), 404); -+ } -+ -+ $this->validate($request, [ -+ 'action' => 'required|in:cw,mark-public,mark-unlisted,mark-private,mark-spammer,delete', -+ ]); -+ -+ $action = $request->input('action'); -+ $status = Status::find($id); -+ -+ if (! $status) { -+ return response()->json(['error' => 'Cannot find status'], 400); -+ } -+ -+ if ($status->uri == null) { -+ if ($status->profile->user && $status->profile->user->is_admin) { -+ return response()->json(['error' => 'Cannot moderate admin accounts'], 400); -+ } -+ } -+ -+ if ($action == 'mark-spammer') { -+ $status->profile->update([ -+ 'unlisted' => true, -+ 'cw' => true, -+ 'no_autolink' => true, -+ ]); -+ -+ Status::whereProfileId($status->profile_id) -+ ->get() -+ ->each(function ($s) { -+ if (in_array($s->scope, ['public', 'unlisted'])) { -+ $s->scope = 'private'; -+ $s->visibility = 'private'; -+ } -+ $s->is_nsfw = true; -+ $s->save(); -+ StatusService::del($s->id, true); -+ }); -+ -+ Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$status->profile_id); -+ Cache::forget('pf:bouncer_v0:recent_by_pid:'.$status->profile_id); -+ Cache::forget('admin-dash:reports:spam-count'); -+ } elseif ($action == 'cw') { -+ $state = $status->is_nsfw; -+ $status->is_nsfw = ! $state; -+ $status->save(); -+ StatusService::del($status->id); -+ } elseif ($action == 'mark-public') { -+ $state = $status->scope; -+ $status->scope = 'public'; -+ $status->visibility = 'public'; -+ $status->save(); -+ StatusService::del($status->id, true); -+ if ($state !== 'public') { -+ if ($status->uri) { -+ if ($status->in_reply_to_id == null && $status->reblog_of_id == null) { -+ NetworkTimelineService::add($status->id); -+ } -+ } else { -+ if ($status->in_reply_to_id == null && $status->reblog_of_id == null) { -+ PublicTimelineService::add($status->id); -+ } -+ } -+ } -+ } elseif ($action == 'mark-unlisted') { -+ $state = $status->scope; -+ $status->scope = 'unlisted'; -+ $status->visibility = 'unlisted'; -+ $status->save(); -+ StatusService::del($status->id); -+ if ($state == 'public') { -+ PublicTimelineService::del($status->id); -+ NetworkTimelineService::del($status->id); -+ } -+ } elseif ($action == 'mark-private') { -+ $state = $status->scope; -+ $status->scope = 'private'; -+ $status->visibility = 'private'; -+ $status->save(); -+ StatusService::del($status->id); -+ if ($state == 'public') { -+ PublicTimelineService::del($status->id); -+ NetworkTimelineService::del($status->id); -+ } -+ } elseif ($action == 'delete') { -+ PublicTimelineService::del($status->id); -+ NetworkTimelineService::del($status->id); -+ Cache::forget('_api:statuses:recent_9:'.$status->profile_id); -+ Cache::forget('profile:status_count:'.$status->profile_id); -+ Cache::forget('profile:embed:'.$status->profile_id); -+ StatusService::del($status->id, true); -+ Cache::forget('profile:status_count:'.$status->profile_id); -+ $status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status); -+ -+ return []; -+ } -+ -+ Cache::forget('_api:statuses:recent_9:'.$status->profile_id); -+ -+ return StatusService::get($status->id, false); -+ } -+ -+ public function getWebSettings(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ -+ $uid = $request->user()->id; -+ $settings = UserSetting::firstOrCreate([ -+ 'user_id' => $uid, -+ ]); -+ if (! $settings->other) { -+ return []; -+ } -+ -+ return $settings->other; -+ } -+ -+ public function setWebSettings(Request $request) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('write'), 403); -+ -+ $this->validate($request, [ -+ 'field' => 'required|in:enable_reblogs,hide_reblog_banner', -+ 'value' => 'required', -+ ]); -+ $field = $request->input('field'); -+ $value = $request->input('value'); -+ $settings = UserSetting::firstOrCreate([ -+ 'user_id' => $request->user()->id, -+ ]); -+ if (! $settings->other) { -+ $other = []; -+ } else { -+ $other = $settings->other; -+ } -+ $other[$field] = $value; -+ $settings->other = $other; -+ $settings->save(); -+ -+ return [200]; -+ } -+ -+ public function getMutualAccounts(Request $request, $id) -+ { -+ abort_if(! $request->user() || ! $request->user()->token(), 403); -+ abort_unless($request->user()->tokenCan('follow'), 403); -+ -+ $account = AccountService::get($id, true); -+ if (! $account || ! isset($account['id'])) { -+ return []; -+ } -+ $res = collect(FollowerService::mutualAccounts($request->user()->profile_id, $id)) -+ ->map(function ($accountId) { -+ return AccountService::get($accountId, true); -+ }) -+ ->filter() -+ ->take(24) -+ ->values(); -+ -+ return $this->json($res); -+ } -+ -+ public function accountUsernameToId(Request $request, $username) -+ { -+ abort_if(! $request->user() || ! $request->user()->token() || ! $username, 403); -+ abort_unless($request->user()->tokenCan('read'), 403); -+ $username = trim($username); -+ $rateLimiting = (bool) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.enabled'); -+ $ipRateLimiting = (bool) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_enabled'); -+ if ($ipRateLimiting) { -+ $userLimit = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_limit'); -+ $userDecay = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_decay'); -+ $userKey = 'pf:apiv1.1:acctU2ID:byIp:'.$request->ip(); -+ -+ if (RateLimiter::tooManyAttempts($userKey, $userLimit)) { -+ $limits = [ -+ 'X-Rate-Limit-Limit' => $userLimit, -+ 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit), -+ 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey), -+ ]; -+ -+ return $this->json(['error' => 'Too many attempts!'], 429, $limits); -+ } -+ -+ RateLimiter::increment($userKey, $userDecay); -+ $limits = [ -+ 'X-Rate-Limit-Limit' => $userLimit, -+ 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit), -+ 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey), -+ ]; -+ } -+ if ($rateLimiting) { -+ $userLimit = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.limit'); -+ $userDecay = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.decay'); -+ $userKey = 'pf:apiv1.1:acctU2ID:byUid:'.$request->user()->id; -+ -+ if (RateLimiter::tooManyAttempts($userKey, $userLimit)) { -+ $limits = [ -+ 'X-Rate-Limit-Limit' => $userLimit, -+ 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit), -+ 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey), -+ ]; -+ -+ return $this->json(['error' => 'Too many attempts!'], 429, $limits); -+ } -+ -+ RateLimiter::increment($userKey, $userDecay); -+ $limits = [ -+ 'X-Rate-Limit-Limit' => $userLimit, -+ 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit), -+ 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey), -+ ]; -+ } -+ if (str_ends_with($username, config_cache('pixelfed.domain.app'))) { -+ $pre = str_starts_with($username, '@') ? substr($username, 1) : $username; -+ $parts = explode('@', $pre); -+ $username = $parts[0]; -+ } -+ $accountId = AccountService::usernameToId($username, true); -+ if (! $accountId) { -+ return []; -+ } -+ $account = AccountService::get($accountId); -+ -+ return $this->json($account, 200, $rateLimiting ? $limits : []); -+ } -+} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 86ee52c8..3e6a9c4f 100644 --- a/app/Http/Controllers/Auth/LoginController.php @@ -1123,17 +104,17 @@ index 86ee52c8..3e6a9c4f 100644 } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php -index 7568fca0..72c8b741 100644 +index 230daea8..1d0e415c 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php -@@ -163,7 +163,7 @@ class RegisterController extends Controller - 'username' => $data['username'], - 'email' => $data['email'], - 'password' => Hash::make($data['password']), -- 'app_register_ip' => request()->ip() -+ 'app_register_ip' => sha1(request()->ip()) - ]); - } +@@ -165,7 +165,7 @@ class RegisterController extends Controller + 'username' => $data['username'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), +- 'app_register_ip' => request()->ip(), ++ 'app_register_ip' => sha1(request()->ip()), + ]); + } diff --git a/app/Http/Controllers/CuratedRegisterController.php b/app/Http/Controllers/CuratedRegisterController.php index 58bddb49..83e2e120 100644 diff --git a/patches/0002-hardcode-discovery-settings.patch b/patches/0002-hardcode-discovery-settings.patch index 66e495b..0f73091 100644 --- a/patches/0002-hardcode-discovery-settings.patch +++ b/patches/0002-hardcode-discovery-settings.patch @@ -1,6 +1,6 @@ -From 5e45b11e0f84ffc83ad55b3bc4969cd7aa9bd805 Mon Sep 17 00:00:00 2001 +From 796abe4146b83b50633e3881cdfa0ddfa2f83202 Mon Sep 17 00:00:00 2001 From: chris -Date: Mon, 1 Jul 2024 12:03:28 +0200 +Date: Sun, 14 Jul 2024 11:08:37 +0200 Subject: [PATCH 2/6] hardcode discovery settings force enable discovery (as dynamic settings are not saved properly) diff --git a/patches/0003-point-to-modified-sourcecode.patch b/patches/0003-point-to-modified-sourcecode.patch index 11d4fb2..0f47ba4 100644 --- a/patches/0003-point-to-modified-sourcecode.patch +++ b/patches/0003-point-to-modified-sourcecode.patch @@ -1,6 +1,6 @@ -From 1f4eb893dc805eeaefdb80c673daea4916ff9857 Mon Sep 17 00:00:00 2001 +From 90df0d4ccc81b1ad2e0d1a14ba9edd85237feeb4 Mon Sep 17 00:00:00 2001 From: chris -Date: Mon, 1 Jul 2024 12:03:45 +0200 +Date: Sun, 14 Jul 2024 11:09:11 +0200 Subject: [PATCH 3/6] point to modified sourcecode as per AGPL license of original source, modifications must be disclosed. diff --git a/patches/0004-disable-beagle-service.patch b/patches/0004-disable-beagle-service.patch index badcc66..0868dc1 100644 --- a/patches/0004-disable-beagle-service.patch +++ b/patches/0004-disable-beagle-service.patch @@ -1,6 +1,6 @@ -From 9c339532d73d5cbd45cb711396d2c42725620f45 Mon Sep 17 00:00:00 2001 +From ebf5c9052746eba97ee6c9abefb2e355b54c2bf7 Mon Sep 17 00:00:00 2001 From: chris -Date: Mon, 1 Jul 2024 12:06:16 +0200 +Date: Sun, 14 Jul 2024 11:09:42 +0200 Subject: [PATCH 4/6] disable beagle service beagle is a remote API service provided by dansup and used for centralised lookups. diff --git a/patches/0005-allow-30-char-usernames.patch b/patches/0005-allow-30-char-usernames.patch index 0648e46..0bf47c7 100644 --- a/patches/0005-allow-30-char-usernames.patch +++ b/patches/0005-allow-30-char-usernames.patch @@ -1,6 +1,6 @@ -From 147ba26fdc854392c32e81778470c9038c288c6d Mon Sep 17 00:00:00 2001 +From 6cf238c78b05243d8ddcdfa7ed7d08ba2292efd5 Mon Sep 17 00:00:00 2001 From: chris -Date: Mon, 1 Jul 2024 12:06:48 +0200 +Date: Sun, 14 Jul 2024 11:10:36 +0200 Subject: [PATCH 5/6] allow 30 char usernames raise maximum username length, because why not? @@ -9,18 +9,18 @@ raise maximum username length, because why not? 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php -index 72c8b741..2c8a26b4 100644 +index 1d0e415c..3150ddba 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php -@@ -70,7 +70,7 @@ class RegisterController extends Controller - $usernameRules = [ - 'required', - 'min:2', -- 'max:15', -+ 'max:30', - 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); +@@ -69,7 +69,7 @@ class RegisterController extends Controller + $usernameRules = [ + 'required', + 'min:2', +- 'max:15', ++ 'max:30', + 'unique:users', + function ($attribute, $value, $fail) { + $dash = substr_count($value, '-'); -- 2.45.2 diff --git a/patches/0006-Link-legal-notice.patch b/patches/0006-Link-legal-notice.patch index 389ebb2..f2ce9c2 100644 --- a/patches/0006-Link-legal-notice.patch +++ b/patches/0006-Link-legal-notice.patch @@ -1,6 +1,6 @@ -From 5e9c2e82672839b3712bc6e372848ad38571b361 Mon Sep 17 00:00:00 2001 +From 7246b1144c638a7ec000b21cf3f1ddf277dc00b5 Mon Sep 17 00:00:00 2001 From: chris -Date: Mon, 1 Jul 2024 12:07:04 +0200 +Date: Sun, 14 Jul 2024 11:11:14 +0200 Subject: [PATCH 6/6] Link legal notice local jurisdiction requires a prominent link to a legal notice at the frontpage