diff --git a/.circleci/config.yml b/.circleci/config.yml index ff8eb4859..83a2088d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 aliases: - &defaults docker: - - image: circleci/ruby:2.6-stretch-node + - image: circleci/ruby:2.6-buster-node environment: &ruby_environment BUNDLE_APP_CONFIG: ./.bundle/ DB_HOST: localhost @@ -39,7 +39,6 @@ aliases: steps: - checkout - *attach_workspace - - restore_cache: keys: - v1-node-dependencies-{{ checksum "yarn.lock" }} @@ -49,7 +48,6 @@ aliases: key: v1-node-dependencies-{{ checksum "yarn.lock" }} paths: - ./node_modules/ - - *persist_to_workspace - &install_system_dependencies @@ -58,13 +56,17 @@ aliases: command: | sudo apt-get update sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler + + ## TODO: FIX THESE BUSTER DEPENDANCES + sudo wget http://ftp.au.debian.org/debian/pool/main/i/icu/libicu57_57.1-6+deb9u3_amd64.deb + sudo dpkg -i libicu57_57.1-6+deb9u3_amd64.deb + sudo wget http://ftp.au.debian.org/debian/pool/main/p/protobuf/libprotobuf10_3.0.0-9_amd64.deb + sudo dpkg -i libprotobuf10_3.0.0-9_amd64.deb - &install_ruby_dependencies steps: - *attach_workspace - - *install_system_dependencies - - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - *restore_ruby_dependencies - run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production && bundle clean @@ -82,10 +84,8 @@ aliases: - &test_steps steps: - *attach_workspace - - *install_system_dependencies - run: sudo apt-get install -y ffmpeg - - run: name: Prepare Tests command: ./bin/rails parallel:create parallel:load_schema parallel:prepare @@ -105,14 +105,14 @@ jobs: install-ruby2.5: <<: *defaults docker: - - image: circleci/ruby:2.5-stretch-node + - image: circleci/ruby:2.5-buster-node environment: *ruby_environment <<: *install_ruby_dependencies install-ruby2.4: <<: *defaults docker: - - image: circleci/ruby:2.4-stretch-node + - image: circleci/ruby:2.4-buster-node environment: *ruby_environment <<: *install_ruby_dependencies @@ -131,7 +131,7 @@ jobs: test-ruby2.6: <<: *defaults docker: - - image: circleci/ruby:2.6-stretch-node + - image: circleci/ruby:2.6-buster-node environment: *ruby_environment - image: circleci/postgres:10.6-alpine environment: @@ -142,7 +142,7 @@ jobs: test-ruby2.5: <<: *defaults docker: - - image: circleci/ruby:2.5-stretch-node + - image: circleci/ruby:2.5-buster-node environment: *ruby_environment - image: circleci/postgres:10.6-alpine environment: @@ -153,7 +153,7 @@ jobs: test-ruby2.4: <<: *defaults docker: - - image: circleci/ruby:2.4-stretch-node + - image: circleci/ruby:2.4-buster-node environment: *ruby_environment - image: circleci/postgres:10.6-alpine environment: @@ -164,7 +164,7 @@ jobs: test-webui: <<: *defaults docker: - - image: circleci/node:12.9-stretch + - image: circleci/node:12-buster steps: - *attach_workspace - run: ./bin/retry yarn test:jest diff --git a/Dockerfile b/Dockerfile index 117727d08..cc75bd6be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep SHELL ["bash", "-c"] # Install Node v12 (LTS) -ENV NODE_VER="12.13.1" +ENV NODE_VER="12.14.0" RUN echo "Etc/UTC" > /etc/localtime && \ apt update && \ apt -y install wget python && \ diff --git a/Gemfile b/Gemfile index bb4e65fdc..a0ad8e464 100644 --- a/Gemfile +++ b/Gemfile @@ -7,11 +7,11 @@ gem 'pkg-config', '~> 1.4' gem 'puma', '~> 4.3' gem 'rails', '~> 5.2.4' -gem 'sprockets', '~> 3.7' +gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 0.20' gem 'hamlit-rails', '~> 0.2' -gem 'pg', '~> 1.1' +gem 'pg', '~> 1.2' gem 'makara', '~> 0.4' gem 'pghero', '~> 2.4' gem 'dotenv-rails', '~> 2.7' @@ -31,7 +31,7 @@ gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'iso-639' gem 'chewy', '~> 5.1' -gem 'cld3', '~> 3.2.4' +gem 'cld3', '~> 3.2.6' gem 'devise', '~> 4.7' gem 'devise-two-factor', '~> 3.1' @@ -50,7 +50,7 @@ gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'goldfinger', '~> 2.1' gem 'hiredis', '~> 0.6' -gem 'redis-namespace', '~> 1.5' +gem 'redis-namespace', '~> 1.7' gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b' gem 'html2text' gem 'htmlentities', '~> 4.3' @@ -61,7 +61,7 @@ gem 'httplog', '~> 1.3' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' -gem 'mime-types', '~> 3.3', require: 'mime/types/columnar' +gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar' gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532' gem 'nokogiri', '~> 1.10' gem 'nsa', '~> 0.2' @@ -120,8 +120,8 @@ end group :test do gem 'capybara', '~> 3.29' gem 'climate_control', '~> 0.2' - gem 'faker', '~> 2.9' - gem 'microformats', '~> 4.1' + gem 'faker', '~> 2.10' + gem 'microformats', '~> 4.2' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.17', require: false diff --git a/Gemfile.lock b/Gemfile.lock index cfa4201fd..fec95d27f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -185,10 +185,10 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.5) - css_parser (1.7.0) + css_parser (1.7.1) addressable debug_inspector (0.0.3) - derailed_benchmarks (1.4.2) + derailed_benchmarks (1.4.3) benchmark-ips (~> 2) get_process_mem (~> 0) heapy (~> 0) @@ -240,9 +240,9 @@ GEM tzinfo excon (0.71.0) fabrication (2.21.0) - faker (2.9.0) + faker (2.10.0) i18n (>= 1.6, < 1.8) - faraday (0.15.4) + faraday (1.0.0) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.7) @@ -275,8 +275,8 @@ GEM http (~> 3.0) nokogiri (~> 1.8) oj (~> 3.0) - hamlit (2.9.3) - temple (>= 0.8.0) + hamlit (2.11.0) + temple (>= 0.8.2) thor tilt hamlit-rails (0.2.3) @@ -374,9 +374,9 @@ GEM microformats (4.1.0) json (~> 2.1) nokogiri (~> 1.8, >= 1.8.3) - mime-types (3.3) + mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0904) + mime-types-data (3.2019.1009) mimemagic (0.3.3) mini_mime (1.0.2) mini_portile2 (2.4.0) @@ -434,7 +434,7 @@ GEM pastel (0.7.3) equatable (~> 0.6) tty-color (~> 0.5) - pg (1.1.4) + pg (1.2.0) pghero (2.4.1) activerecord (>= 5) pkg-config (1.4.0) @@ -454,7 +454,7 @@ GEM pry (~> 0.10) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (4.0.1) + public_suffix (4.0.2) puma (4.3.1) nio4r (~> 2.0) pundit (2.1.0) @@ -463,7 +463,7 @@ GEM rack (2.0.8) rack-attack (6.2.2) rack (>= 1.0, < 3) - rack-cors (1.1.0) + rack-cors (1.1.1) rack (>= 2.0.0) rack-protection (2.0.7) rack @@ -520,7 +520,7 @@ GEM redis-activesupport (5.0.4) activesupport (>= 3, < 6) redis-store (>= 1.3, < 2) - redis-namespace (1.6.0) + redis-namespace (1.7.0) redis (>= 3.0.4) redis-rack (2.0.4) rack (>= 1.5, < 3) @@ -532,7 +532,7 @@ GEM redis-store (1.5.0) redis (>= 2.2, < 5) regexp_parser (1.6.0) - request_store (1.4.1) + request_store (1.5.0) rack (>= 1.4) responders (3.0.0) actionpack (>= 5.0) @@ -618,21 +618,21 @@ GEM sshkit (1.20.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - stackprof (0.2.14) + stackprof (0.2.15) statsd-ruby (1.4.0) stoplight (2.2.0) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) strong_migrations (0.5.1) activerecord (>= 5) - temple (0.8.1) + temple (0.8.2) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) thor (0.20.3) thread_safe (0.3.6) - tilt (2.0.9) + tilt (2.0.10) tty-color (0.5.0) tty-command (0.9.0) pastel (~> 0.7.0) @@ -654,7 +654,7 @@ GEM tzinfo (>= 1.0.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.5) + unf_ext (0.0.7.6) unicode-display_width (1.6.0) uniform_notifier (1.12.1) warden (1.2.8) @@ -713,7 +713,7 @@ DEPENDENCIES doorkeeper (~> 5.2) dotenv-rails (~> 2.7) fabrication (~> 2.21) - faker (~> 2.9) + faker (~> 2.10) fast_blank (~> 1.0) fastimage fog-core (<= 2.1.0) @@ -759,7 +759,7 @@ DEPENDENCIES parallel (~> 1.19) parallel_tests (~> 2.30) parslet - pg (~> 1.1) + pg (~> 1.2) pghero (~> 2.4) pkg-config (~> 1.4) posix-spawn! @@ -778,7 +778,7 @@ DEPENDENCIES rdf-normalize (~> 0.3) redcarpet (~> 3.4) redis (~> 4.1) - redis-namespace (~> 1.5) + redis-namespace (~> 1.7) redis-rails (~> 5.0) rqrcode (~> 0.10) rspec-rails (~> 3.9) @@ -808,9 +808,3 @@ DEPENDENCIES webmock (~> 3.7) webpacker (~> 4.2) webpush - -RUBY VERSION - ruby 2.6.5p114 - -BUNDLED WITH - 1.17.3 diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index 2af90f051..a446465c9 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -2,10 +2,6 @@ module Admin class CustomEmojisController < BaseController - include ObfuscateFilename - - obfuscate_filename [:custom_emoji, :image] - def index authorize :custom_emoji, :index? diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 2dabb8398..e360b8a92 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController def load_accounts return [] if hide_results? - default_accounts.merge(paginated_follows).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_follows).to_a end def hide_results? - (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account)) + (@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 44e89804b..a405b365f 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController def load_accounts return [] if hide_results? - default_accounts.merge(paginated_follows).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_follows).to_a end def hide_results? - (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account)) + (@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb index aaa93b615..81825db15 100644 --- a/app/controllers/api/v1/media_controller.rb +++ b/app/controllers/api/v1/media_controller.rb @@ -4,9 +4,6 @@ class Api::V1::MediaController < Api::BaseController before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action :require_user! - include ObfuscateFilename - obfuscate_filename :file - respond_to :json def create diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index 657e57831..99eff360e 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -17,7 +17,9 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController private def load_accounts - default_accounts.merge(paginated_favourites).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_favourites).to_a end def default_accounts diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index 6851099f6..cc285ad23 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -17,7 +17,9 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController private def load_accounts - default_accounts.merge(paginated_statuses).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_statuses).to_a end def default_accounts diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f1a4f0d02..c882d40ab 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -25,6 +25,7 @@ class ApplicationController < ActionController::Base rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity rescue_from ActionController::UnknownFormat, with: :not_acceptable rescue_from ActionController::ParameterMissing, with: :bad_request + rescue_from Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error @@ -211,7 +212,12 @@ class ApplicationController < ActionController::Base end def respond_with_error(code) - use_pack 'error' - render "errors/#{code}", layout: 'error', status: code, formats: [:html] + respond_to do |format| + format.any do + use_pack 'error' + render "errors/#{code}", layout: 'error', status: code, formats: [:html] + end + format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code } + end end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 068375843..a9d075a45 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -11,6 +11,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update] before_action :require_not_suspended!, only: [:update] + before_action :set_cache_headers, only: [:edit, :update] skip_before_action :require_functional!, only: [:edit, :update] @@ -114,4 +115,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController def require_not_suspended! forbidden if current_account.suspended? end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end end diff --git a/app/controllers/concerns/obfuscate_filename.rb b/app/controllers/concerns/obfuscate_filename.rb deleted file mode 100644 index 22736ec3a..000000000 --- a/app/controllers/concerns/obfuscate_filename.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module ObfuscateFilename - extend ActiveSupport::Concern - - class_methods do - def obfuscate_filename(path) - before_action do - file = params.dig(*path) - next if file.nil? - - file.original_filename = SecureRandom.hex(8) + File.extname(file.original_filename) - end - end - end -end diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb index f1e110d87..76be03e53 100644 --- a/app/controllers/filters_controller.rb +++ b/app/controllers/filters_controller.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true class FiltersController < ApplicationController - include Authorization - layout 'admin' + before_action :authenticate_user! before_action :set_filters, only: :index before_action :set_filter, only: [:edit, :update, :destroy] before_action :set_pack diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index df46f5f72..a5dfffd6d 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -19,7 +19,6 @@ class FollowerAccountsController < ApplicationController next if @account.user_hides_network? follows - @relationships = AccountRelationshipsPresenter.new(follows.map(&:account_id), current_user.account_id) if user_signed_in? end format.json do @@ -38,7 +37,11 @@ class FollowerAccountsController < ApplicationController private def follows - @follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account) + return @follows if defined?(@follows) + + scope = Follow.where(target_account: @account) + scope = scope.where.not(account_id: current_account.excluded_from_timeline_account_ids) if user_signed_in? + @follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account) end def page_requested? diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 8cab67ff5..ff23d97f9 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -19,7 +19,6 @@ class FollowingAccountsController < ApplicationController next if @account.user_hides_network? follows - @relationships = AccountRelationshipsPresenter.new(follows.map(&:target_account_id), current_user.account_id) if user_signed_in? end format.json do @@ -38,7 +37,11 @@ class FollowingAccountsController < ApplicationController private def follows - @follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account) + return @follows if defined?(@follows) + + scope = Follow.where(account: @account) + scope = scope.where.not(target_account_id: current_account.excluded_from_timeline_account_ids) if user_signed_in? + @follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account) end def page_requested? diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb index f6f5d1ecc..137346ed0 100644 --- a/app/controllers/oauth/authorizations_controller.rb +++ b/app/controllers/oauth/authorizations_controller.rb @@ -6,6 +6,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController before_action :store_current_location before_action :authenticate_resource_owner! before_action :set_pack + before_action :set_cache_headers include Localized @@ -32,4 +33,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController def truthy_param?(key) ActiveModel::Type::Boolean.new.cast(params[key]) end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end end diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb index 8c394a6d3..b97603af6 100644 --- a/app/controllers/settings/base_controller.rb +++ b/app/controllers/settings/base_controller.rb @@ -3,6 +3,7 @@ class Settings::BaseController < ApplicationController before_action :set_pack before_action :set_body_classes + before_action :set_cache_headers private @@ -13,4 +14,8 @@ class Settings::BaseController < ApplicationController def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 8b640cdca..19a7ce157 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -1,16 +1,11 @@ # frozen_string_literal: true class Settings::ProfilesController < Settings::BaseController - include ObfuscateFilename - layout 'admin' before_action :authenticate_user! before_action :set_account - obfuscate_filename [:account, :avatar] - obfuscate_filename [:account, :header] - def show @account.build_fields end diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb index 2e9298c4a..2fd6bc7cc 100644 --- a/app/controllers/well_known/host_meta_controller.rb +++ b/app/controllers/well_known/host_meta_controller.rb @@ -8,12 +8,8 @@ module WellKnown def show @webfinger_template = "#{webfinger_url}?resource={uri}" - - respond_to do |format| - format.xml { render content_type: 'application/xrd+xml' } - end - expires_in 3.days, public: true + render content_type: 'application/xrd+xml', formats: [:xml] end end end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index 7fcc4e816..986fd1805 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -13,7 +13,7 @@ module AccountsHelper if account.local? "@#{account.acct}@#{Rails.configuration.x.local_domain}" else - "@#{account.acct}" + "@#{account.pretty_acct}" end end diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 06a19afc3..5640201c6 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -26,8 +26,9 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST'; export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS'; export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; -export const STATUS_REVEAL = 'STATUS_REVEAL'; -export const STATUS_HIDE = 'STATUS_HIDE'; +export const STATUS_REVEAL = 'STATUS_REVEAL'; +export const STATUS_HIDE = 'STATUS_HIDE'; +export const STATUS_COLLAPSE = 'STATUS_COLLAPSE'; export const REDRAFT = 'REDRAFT'; @@ -320,3 +321,11 @@ export function revealStatus(ids) { ids, }; }; + +export function toggleStatusCollapse(id, isCollapsed) { + return { + type: STATUS_COLLAPSE, + id, + isCollapsed, + }; +} diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index 421756803..47a87b149 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -208,10 +208,13 @@ export default class ScrollableList extends PureComponent { } attachIntersectionObserver () { - this.intersectionObserverWrapper.connect({ + let nodeOptions = { root: this.node, rootMargin: '300% 0px', - }); + }; + + this.intersectionObserverWrapper + .connect(this.props.bindToDocument ? {} : nodeOptions); } detachIntersectionObserver () { diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index e120278a0..0dc00cb98 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -76,6 +76,7 @@ class Status extends ImmutablePureComponent { onEmbed: PropTypes.func, onHeightChange: PropTypes.func, onToggleHidden: PropTypes.func, + onToggleCollapsed: PropTypes.func, muted: PropTypes.bool, hidden: PropTypes.bool, unread: PropTypes.bool, @@ -102,19 +103,6 @@ class Status extends ImmutablePureComponent { statusId: undefined, }; - // Track height changes we know about to compensate scrolling - componentDidMount () { - this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); - } - - getSnapshotBeforeUpdate () { - if (this.props.getScrollPosition) { - return this.props.getScrollPosition(); - } else { - return null; - } - } - static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) { return { @@ -126,32 +114,6 @@ class Status extends ImmutablePureComponent { } } - // Compensate height changes - componentDidUpdate (prevProps, prevState, snapshot) { - const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); - - if (doShowCard && !this.didShowCard) { - this.didShowCard = true; - - if (snapshot !== null && this.props.updateScrollBottom) { - if (this.node && this.node.offsetTop < snapshot.top) { - this.props.updateScrollBottom(snapshot.height - snapshot.top); - } - } - } - } - - componentWillUnmount() { - if (this.node && this.props.getScrollPosition) { - const position = this.props.getScrollPosition(); - if (position !== null && this.node.offsetTop < position.top) { - requestAnimationFrame(() => { - this.props.updateScrollBottom(position.height - position.top); - }); - } - } - } - handleToggleMediaVisibility = () => { this.setState({ showMedia: !this.state.showMedia }); } @@ -196,7 +158,11 @@ class Status extends ImmutablePureComponent { handleExpandedToggle = () => { this.props.onToggleHidden(this._properStatus()); - }; + } + + handleCollapsedToggle = isCollapsed => { + this.props.onToggleCollapsed(this._properStatus(), isCollapsed); + } renderLoadingMediaGallery () { return
; @@ -466,7 +432,7 @@ class Status extends ImmutablePureComponent {
- + {media} diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index d13091325..5d921fd41 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -23,11 +23,11 @@ export default class StatusContent extends React.PureComponent { onExpandedToggle: PropTypes.func, onClick: PropTypes.func, collapsable: PropTypes.bool, + onCollapsedToggle: PropTypes.func, }; state = { hidden: true, - collapsed: null, // `collapsed: null` indicates that an element doesn't need collapsing, while `true` or `false` indicates that it does (and is/isn't). }; _updateStatusLinks () { @@ -62,14 +62,16 @@ export default class StatusContent extends React.PureComponent { link.setAttribute('rel', 'noopener noreferrer'); } - if ( - this.props.collapsable - && this.props.onClick - && this.state.collapsed === null - && node.clientHeight > MAX_HEIGHT - && this.props.status.get('spoiler_text').length === 0 - ) { - this.setState({ collapsed: true }); + if (this.props.status.get('collapsed', null) === null) { + let collapsed = + this.props.collapsable + && this.props.onClick + && node.clientHeight > MAX_HEIGHT + && this.props.status.get('spoiler_text').length === 0; + + if(this.props.onCollapsedToggle) this.props.onCollapsedToggle(collapsed); + + this.props.status.set('collapsed', collapsed); } } @@ -178,6 +180,7 @@ export default class StatusContent extends React.PureComponent { } const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; + const renderReadMore = this.props.onClick && status.get('collapsed'); const content = { __html: status.get('contentHtml') }; const spoilerContent = { __html: status.get('spoilerHtml') }; @@ -185,7 +188,7 @@ export default class StatusContent extends React.PureComponent { const classNames = classnames('status__content', { 'status__content--with-action': this.props.onClick && this.context.router, 'status__content--with-spoiler': status.get('spoiler_text').length > 0, - 'status__content--collapsed': this.state.collapsed === true, + 'status__content--collapsed': renderReadMore, }); if (isRtl(status.get('search_index'))) { @@ -237,7 +240,7 @@ export default class StatusContent extends React.PureComponent { , ]; - if (this.state.collapsed) { + if (renderReadMore) { output.push(readMoreButton); } diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 35c16a20c..2ba3a3123 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -23,6 +23,7 @@ import { deleteStatus, hideStatus, revealStatus, + toggleStatusCollapse, } from '../actions/statuses'; import { unmuteAccount, @@ -190,6 +191,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onToggleCollapsed (status, isCollapsed) { + dispatch(toggleStatusCollapse(status.get('id'), isCollapsed)); + }, + onBlockDomain (domain) { dispatch(openModal('CONFIRM', { message: {domain} }} />, diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 772f98bcb..398a48cff 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -12,6 +12,7 @@ import { STATUS_UNMUTE_SUCCESS, STATUS_REVEAL, STATUS_HIDE, + STATUS_COLLAPSE, } from '../actions/statuses'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; @@ -73,6 +74,8 @@ export default function statuses(state = initialState, action) { } }); }); + case STATUS_COLLAPSE: + return state.setIn([action.id, 'collapsed'], action.isCollapsed); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); default: diff --git a/app/javascript/mastodon/utils/numbers.js b/app/javascript/mastodon/utils/numbers.js index f7e4ceb93..af18dcfdd 100644 --- a/app/javascript/mastodon/utils/numbers.js +++ b/app/javascript/mastodon/utils/numbers.js @@ -4,9 +4,13 @@ import { FormattedNumber } from 'react-intl'; export const shortNumberFormat = number => { if (number < 1000) { return ; - } else if (number < 1000000) { + } else if (number < 10000) { return K; - } else { + } else if (number < 1000000) { + return K; + } else if (number < 10000000) { return M; + } else { + return M; } }; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 01a633c5f..1a5d9e3e3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6399,13 +6399,13 @@ noscript { &__links { font-size: 14px; color: $darker-text-color; + padding: 10px 0; a { display: inline-block; color: $darker-text-color; text-decoration: none; - padding: 10px; - padding-top: 20px; + padding: 5px 10px; font-weight: 500; strong { diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index 6a299f59d..e07ebfffe 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -78,7 +78,7 @@ class SearchQueryTransformer < Parslet::Transform elsif clause[:shortcode] TermClause.new(prefix, operator, ":#{clause[:term]}:") elsif clause[:phrase] - PhraseClause.new(prefix, operator, clause[:phrase].map { |p| p[:term].to_s }.join(' ')) + PhraseClause.new(prefix, operator, clause[:phrase].is_a?(Array) ? clause[:phrase].map { |p| p[:term].to_s }.join(' ') : clause[:phrase].to_s) else raise "Unexpected clause type: #{clause}" end diff --git a/app/middleware/handle_bad_encoding_middleware.rb b/app/middleware/handle_bad_encoding_middleware.rb new file mode 100644 index 000000000..6fce84b15 --- /dev/null +++ b/app/middleware/handle_bad_encoding_middleware.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true +# See: https://jamescrisp.org/2018/05/28/fixing-invalid-query-parameters-invalid-encoding-in-a-rails-app/ + +class HandleBadEncodingMiddleware + def initialize(app) + @app = app + end + + def call(env) + begin + Rack::Utils.parse_nested_query(env['QUERY_STRING'].to_s) + rescue Rack::Utils::InvalidParameterError + env['QUERY_STRING'] = '' + end + + @app.call(env) + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 25cde6d6c..86f7295bc 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -50,7 +50,7 @@ class Account < ApplicationRecord USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i - MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i + MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[a-z0-9]+)?)/i include AccountAssociations include AccountAvatar @@ -168,6 +168,10 @@ class Account < ApplicationRecord local? ? username : "#{username}@#{domain}" end + def pretty_acct + local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}" + end + def local_username_and_domain "#{username}@#{Rails.configuration.x.local_domain}" end diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index 3bbc6453c..1e8c4806f 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -9,6 +9,7 @@ module Attachmentable GIF_MATRIX_LIMIT = 921_600 # 1280x720px included do + before_post_process :obfuscate_file_name before_post_process :set_file_extensions before_post_process :check_image_dimensions before_post_process :set_file_content_type @@ -68,4 +69,14 @@ module Attachmentable rescue Terrapin::CommandLineError '' end + + def obfuscate_file_name + self.class.attachment_definitions.each_key do |attachment_name| + attachment = send(attachment_name) + + next if attachment.blank? + + attachment.instance_write :file_name, SecureRandom.hex(8) + File.extname(attachment.instance_read(:file_name)) + end + end end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 880599028..b87b1b9d3 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -202,9 +202,12 @@ class MediaAttachment < ApplicationRecord end after_commit :reset_parent_cache, on: :update + before_create :prepare_description, unless: :local? before_create :set_shortcode + before_post_process :set_type_and_extension + before_save :set_meta class << self diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 7bdb5d7ff..657dd36f3 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -24,6 +24,10 @@ class REST::AccountSerializer < ActiveModel::Serializer object.id.to_s end + def acct + object.pretty_acct + end + def note Formatter.instance.simplified_format(object) end diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 0b57b6d0c..ab6d090a0 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -166,7 +166,7 @@ class BackupService < BaseService io.write(buffer) end end - rescue Errno::ENOENT + rescue Errno::ENOENT, Seahorse::Client::NetworkingError Rails.logger.warn "Could not backup file #{filename}: file not found" end end diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 19de37717..3c257451c 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -14,7 +14,16 @@ class ProcessMentionsService < BaseService mentions = [] status.text = status.text.gsub(Account::MENTION_RE) do |match| - username, domain = Regexp.last_match(1).split('@') + username, domain = Regexp.last_match(1).split('@') + + domain = begin + if TagManager.instance.local_domain?(domain) + nil + else + TagManager.instance.normalize_domain(domain) + end + end + mentioned_account = Account.find_remote(username, domain) if mention_undeliverable?(mentioned_account) diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index 79b1bad0c..1a2b0d60c 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -12,6 +12,8 @@ class ResolveURLService < BaseService process_local_url elsif !fetched_resource.nil? process_url + elsif @on_behalf_of.present? + process_url_from_db end end @@ -24,15 +26,19 @@ class ResolveURLService < BaseService status = FetchRemoteStatusService.new.call(resource_url, body) authorize_with @on_behalf_of, status, :show? unless status.nil? status - elsif fetched_resource.nil? && @on_behalf_of.present? - # It may happen that the resource is a private toot, and thus not fetchable, - # but we can return the toot if we already know about it. - status = Status.find_by(uri: @url) || Status.find_by(url: @url) - authorize_with @on_behalf_of, status, :show? unless status.nil? - status end end + def process_url_from_db + # It may happen that the resource is a private toot, and thus not fetchable, + # but we can return the toot if we already know about it. + status = Status.find_by(uri: @url) || Status.find_by(url: @url) + authorize_with @on_behalf_of, status, :show? unless status.nil? + status + rescue Mastodon::NotPermittedError + nil + end + def fetched_resource @fetched_resource ||= FetchResourceService.new.call(@url) end diff --git a/app/views/admin/tags/_tag.html.haml b/app/views/admin/tags/_tag.html.haml index 670f3bc05..287d28e53 100644 --- a/app/views/admin/tags/_tag.html.haml +++ b/app/views/admin/tags/_tag.html.haml @@ -1,6 +1,7 @@ .batch-table__row - %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox - = f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id + - if batch_available + %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox + = f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id .directory__tag = link_to admin_tag_path(tag.id) do diff --git a/app/views/admin/tags/index.html.haml b/app/views/admin/tags/index.html.haml index 298ac59e9..aa4f4c297 100644 --- a/app/views/admin/tags/index.html.haml +++ b/app/views/admin/tags/index.html.haml @@ -47,25 +47,26 @@ .batch-table.optional .batch-table__toolbar - %label.batch-table__toolbar__select.batch-checkbox-all - = check_box_tag :batch_checkbox_all, nil, false - .batch-table__toolbar__actions - - if params[:pending_review] == '1' + - if params[:pending_review] == '1' || params[:unreviewed] == '1' + %label.batch-table__toolbar__select.batch-checkbox-all + = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - - else + - else + .batch-table__toolbar__actions %span.neutral-hint= t('generic.no_batch_actions_available') .batch-table__body - if @tags.empty? = nothing_here 'nothing-here--under-tabs' - else - = render partial: 'tag', collection: @tags, locals: { f: f } + = render partial: 'tag', collection: @tags, locals: { f: f, batch_available: params[:pending_review] == '1' || params[:unreviewed] == '1' } = paginate @tags -- if params[:pending_review] == '1' +- if params[:pending_review] == '1' || params[:unreviewed] == '1' %hr.spacer/ %div{ style: 'overflow: hidden' } diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index f1e3d2e97..f460cebba 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -5,6 +5,10 @@ .fields-group = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, selected: I18n.locale, hint: false + - unless I18n.locale == :en + .flash-message{ style: "text-align: unset; color: unset" } + #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener", style: "text-decoration: underline")} + %h4= t 'appearance.advanced_web_interface' %p.hint= t 'appearance.advanced_web_interface_hint' diff --git a/app/views/statuses/embed.html.haml b/app/views/statuses/embed.html.haml index 6f2ec646f..2f111f53f 100644 --- a/app/views/statuses/embed.html.haml +++ b/app/views/statuses/embed.html.haml @@ -1,3 +1,2 @@ -- cache @status do - .activity-stream.activity-stream--headless - = render 'status', status: @status, centered: true, autoplay: @autoplay +.activity-stream.activity-stream--headless + = render 'status', status: @status, centered: true, autoplay: @autoplay diff --git a/app/workers/refollow_worker.rb b/app/workers/refollow_worker.rb index 12f2bf671..9b07ce1b5 100644 --- a/app/workers/refollow_worker.rb +++ b/app/workers/refollow_worker.rb @@ -7,15 +7,18 @@ class RefollowWorker def perform(target_account_id) target_account = Account.find(target_account_id) - return unless target_account.protocol == :activitypub + return unless target_account.activitypub? + + target_account.passive_relationships.where(account: Account.where(domain: nil)).includes(:account).reorder(nil).find_each do |follow| + reblogs = follow.show_reblogs? - target_account.followers.where(domain: nil).reorder(nil).find_each do |follower| # Locally unfollow remote account + follower = follow.account follower.unfollow!(target_account) # Schedule re-follow begin - FollowService.new.call(follower, target_account) + FollowService.new.call(follower, target_account, reblogs: reblogs) rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError next end diff --git a/config/application.rb b/config/application.rb index e1f7ae707..58e59fd51 100644 --- a/config/application.rb +++ b/config/application.rb @@ -7,6 +7,7 @@ require 'rails/all' Bundler.require(*Rails.groups) require_relative '../app/lib/exceptions' +require_relative '../app/middleware/handle_bad_encoding_middleware' require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/video_transcoder' @@ -118,6 +119,7 @@ module Mastodon config.active_job.queue_adapter = :sidekiq + config.middleware.insert_before Rack::Runtime, HandleBadEncodingMiddleware config.middleware.use Rack::Attack config.middleware.use Rack::Deflater diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 7784bec62..e03380cec 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -8,20 +8,15 @@ Doorkeeper.configure do end resource_owner_from_credentials do |_routes| - if Devise.ldap_authentication - user = User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] }) - end - - if Devise.pam_authentication - user ||= User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] }) - end + user = User.authenticate_with_ldap(email: request.params[:username], password: request.params[:password]) if Devise.ldap_authentication + user ||= User.authenticate_with_pam(email: request.params[:username], password: request.params[:password]) if Devise.pam_authentication if user.nil? user = User.find_by(email: request.params[:username]) - user = nil unless user.valid_password?(request.params[:password]) + user = nil unless user&.valid_password?(request.params[:password]) end - user if !user&.otp_required_for_login? + user unless user&.otp_required_for_login? end # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index 5109baff7..8909678d6 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +Paperclip::DataUriAdapter.register + Paperclip.interpolates :filename do |attachment, style| if style == :original attachment.original_filename diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 273cac9ca..3cd7ea3a6 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -46,10 +46,7 @@ class Rack::Attack PROTECTED_PATHS_REGEX = Regexp.union(PROTECTED_PATHS.map { |path| /\A#{Regexp.escape(path)}/ }) - # Always allow requests from localhost - # (blocklist & throttles are skipped) Rack::Attack.safelist('allow from localhost') do |req| - # Requests are allowed if the return value is truthy req.remote_ip == '127.0.0.1' || req.remote_ip == '::1' end diff --git a/config/locales/en.yml b/config/locales/en.yml index 43c24fc4e..74f397a3f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -594,6 +594,10 @@ en: animations_and_accessibility: Animations and accessibility confirmation_dialogs: Confirmation dialogs discovery: Discovery + localization: + body: Mastodon is translated by volunteers. + guide_link: https://crowdin.com/project/mastodon + guide_link_text: Everyone can contribute. sensitive_content: Sensitive content toot_layout: Toot layout application_mailer: diff --git a/db/migrate/20170711225116_fix_null_booleans.rb b/db/migrate/20170711225116_fix_null_booleans.rb index 5b319471d..aabb81f21 100644 --- a/db/migrate/20170711225116_fix_null_booleans.rb +++ b/db/migrate/20170711225116_fix_null_booleans.rb @@ -1,17 +1,19 @@ class FixNullBooleans < ActiveRecord::Migration[5.1] def change - change_column_default :domain_blocks, :reject_media, false - change_column_null :domain_blocks, :reject_media, false, false + safety_assured do + change_column_default :domain_blocks, :reject_media, false + change_column_null :domain_blocks, :reject_media, false, false - change_column_default :imports, :approved, false - change_column_null :imports, :approved, false, false + change_column_default :imports, :approved, false + change_column_null :imports, :approved, false, false - change_column_null :statuses, :sensitive, false, false - change_column_null :statuses, :reply, false, false + change_column_null :statuses, :sensitive, false, false + change_column_null :statuses, :reply, false, false - change_column_null :users, :admin, false, false + change_column_null :users, :admin, false, false - change_column_default :users, :otp_required_for_login, false - change_column_null :users, :otp_required_for_login, false, false + change_column_default :users, :otp_required_for_login, false + change_column_null :users, :otp_required_for_login, false, false + end end end diff --git a/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb b/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb index 747e5a826..1d7a0086c 100644 --- a/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb +++ b/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb @@ -1,6 +1,8 @@ class ChangeAccountsNonnullableInAccountModerationNotes < ActiveRecord::Migration[5.1] def change - change_column_null :account_moderation_notes, :account_id, false - change_column_null :account_moderation_notes, :target_account_id, false + safety_assured do + change_column_null :account_moderation_notes, :account_id, false + change_column_null :account_moderation_notes, :target_account_id, false + end end end diff --git a/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb b/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb index 3369e3b9e..ac86c9e77 100644 --- a/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb +++ b/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb @@ -1,5 +1,7 @@ class ChangeAccountIdNonnullableInLists < ActiveRecord::Migration[5.1] def change - change_column_null :lists, :account_id, false + safety_assured do + change_column_null :lists, :account_id, false + end end end diff --git a/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb b/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb index 05ffd0501..dba789207 100644 --- a/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb +++ b/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb @@ -1,8 +1,10 @@ class ChangeColumnsInNotificationsNonnullable < ActiveRecord::Migration[5.1] def change - change_column_null :notifications, :activity_id, false - change_column_null :notifications, :activity_type, false - change_column_null :notifications, :account_id, false - change_column_null :notifications, :from_account_id, false + safety_assured do + change_column_null :notifications, :activity_id, false + change_column_null :notifications, :activity_type, false + change_column_null :notifications, :account_id, false + change_column_null :notifications, :from_account_id, false + end end end diff --git a/package.json b/package.json index 9e0c44550..71cab74ab 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "private": true, "dependencies": { - "@babel/core": "^7.7.5", + "@babel/core": "^7.7.7", "@babel/plugin-proposal-class-properties": "^7.7.4", "@babel/plugin-proposal-decorators": "^7.7.4", "@babel/plugin-transform-react-inline-elements": "^7.7.4", @@ -69,7 +69,7 @@ "@babel/preset-react": "^7.7.4", "@babel/runtime": "^7.7.7", "@gamestdio/websocket": "^0.3.2", - "@clusterws/cws": "^0.16.0", + "@clusterws/cws": "^0.16.1", "array-includes": "^3.1.1", "atrament": "^0.2.3", "arrow-key-navigation": "^1.1.0", @@ -107,7 +107,7 @@ "intl": "^1.2.5", "intl-messageformat": "^2.2.0", "intl-relativeformat": "^6.4.3", - "is-nan": "^1.2.1", + "is-nan": "^1.3.0", "js-yaml": "^3.13.1", "lodash": "^4.17.14", "mark-loader": "^0.1.6", @@ -146,7 +146,7 @@ "react-textarea-autosize": "^7.1.2", "react-toggle": "^4.1.1", "redis": "^2.7.1", - "redux": "^4.0.4", + "redux": "^4.0.5", "redux-immutable": "^4.0.0", "redux-thunk": "^2.2.0", "rellax": "^1.10.0", @@ -157,13 +157,13 @@ "sass-loader": "^8.0.0", "stringz": "^2.0.0", "substring-trie": "^1.0.2", - "terser-webpack-plugin": "^2.2.2", + "terser-webpack-plugin": "^2.3.1", "tesseract.js": "^2.0.0-alpha.16", "throng": "^4.0.0", "tiny-queue": "^0.2.1", "uuid": "^3.3.3", "wavesurfer.js": "^3.2.0", - "webpack": "^4.41.2", + "webpack": "^4.41.5", "webpack-assets-manifest": "^3.1.1", "webpack-bundle-analyzer": "^3.6.0", "webpack-cli": "^3.3.10", @@ -175,7 +175,7 @@ "babel-jest": "^24.9.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.1", - "eslint": "^6.7.2", + "eslint": "^6.8.0", "eslint-plugin-import": "~2.19.1", "eslint-plugin-jsx-a11y": "~6.2.3", "eslint-plugin-promise": "~4.2.1", @@ -183,7 +183,7 @@ "jest": "^24.9.0", "raf": "^3.4.1", "react-intl-translations-manager": "^5.0.3", - "react-test-renderer": "^16.11.0", + "react-test-renderer": "^16.12.0", "sass-lint": "^1.13.1", "webpack-dev-server": "^3.9.0", "yargs": "^15.0.2" diff --git a/spec/controllers/api/proofs_controller_spec.rb b/spec/controllers/api/proofs_controller_spec.rb index dbde4927f..2fe615005 100644 --- a/spec/controllers/api/proofs_controller_spec.rb +++ b/spec/controllers/api/proofs_controller_spec.rb @@ -85,10 +85,7 @@ describe Api::ProofsController do end it 'has the correct avatar url' do - first_part = 'https://cb6e6126.ngrok.io/system/accounts/avatars/' - last_part = 'original/avatar.gif' - - expect(body_as_json[:avatar]).to match /#{Regexp.quote(first_part)}(?:\d{3,5}\/){3}#{Regexp.quote(last_part)}/ + expect(body_as_json[:avatar]).to match "https://cb6e6126.ngrok.io#{alice.avatar.url}" end end end diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb index 75e0570e9..54587187f 100644 --- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb @@ -3,19 +3,38 @@ require 'rails_helper' describe Api::V1::Accounts::FollowerAccountsController do render_views - let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:account) { Fabricate(:account) } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } before do - Fabricate(:follow, target_account: user.account) + alice.follow!(account) + bob.follow!(account) allow(controller).to receive(:doorkeeper_token) { token } end describe 'GET #index' do it 'returns http success' do - get :index, params: { account_id: user.account.id, limit: 1 } + get :index, params: { account_id: account.id, limit: 2 } expect(response).to have_http_status(200) end + + it 'returns accounts following the given account' do + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb index 7f7105ad3..a580a7368 100644 --- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb @@ -3,19 +3,38 @@ require 'rails_helper' describe Api::V1::Accounts::FollowingAccountsController do render_views - let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:account) { Fabricate(:account) } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } before do - Fabricate(:follow, account: user.account) + account.follow!(alice) + account.follow!(bob) allow(controller).to receive(:doorkeeper_token) { token } end describe 'GET #index' do it 'returns http success' do - get :index, params: { account_id: user.account.id, limit: 1 } + get :index, params: { account_id: account.id, limit: 2 } expect(response).to have_http_status(200) end + + it 'returns accounts followed by the given account' do + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb index 40f75c700..f053ae573 100644 --- a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb @@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } context 'with an oauth token' do before do @@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control let(:status) { Fabricate(:status, account: user.account) } before do - Fabricate(:favourite, status: status) + Favourite.create!(account: alice, status: status) + Favourite.create!(account: bob, status: status) end it 'returns http success' do - get :index, params: { status_id: status.id, limit: 1 } + get :index, params: { status_id: status.id, limit: 2 } expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end + + it 'returns accounts who favorited the status' do + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb index d758786dc..60908b7b3 100644 --- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb @@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } context 'with an oauth token' do before do @@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll let(:status) { Fabricate(:status, account: user.account) } before do - Fabricate(:status, reblog_of_id: status.id) + Fabricate(:status, account: alice, reblog_of_id: status.id) + Fabricate(:status, account: bob, reblog_of_id: status.id) end it 'returns http success' do - get :index, params: { status_id: status.id, limit: 1 } + get :index, params: { status_id: status.id, limit: 2 } expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end + + it 'returns accounts who reblogged the status' do + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/concerns/obfuscate_filename_spec.rb b/spec/controllers/concerns/obfuscate_filename_spec.rb deleted file mode 100644 index e06d53c03..000000000 --- a/spec/controllers/concerns/obfuscate_filename_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe ApplicationController, type: :controller do - controller do - include ObfuscateFilename - - obfuscate_filename :file - - def file - render plain: params[:file]&.original_filename - end - end - - before do - routes.draw { get 'file' => 'anonymous#file' } - end - - it 'obfusticates filename if the given parameter is specified' do - file = fixture_file_upload('files/imports.txt', 'text/plain') - post 'file', params: { file: file } - expect(response.body).to end_with '.txt' - expect(response.body).not_to include 'imports' - end - - it 'does nothing if the given parameter is not specified' do - post 'file' - end -end diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb index 83032ab64..34a0cf3f4 100644 --- a/spec/controllers/follower_accounts_controller_spec.rb +++ b/spec/controllers/follower_accounts_controller_spec.rb @@ -22,6 +22,18 @@ describe FollowerAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 end + + it 'does not assign blocked users' do + user = Fabricate(:user) + user.account.block!(follower0) + sign_in(user) + + expect(response).to have_http_status(200) + + assigned = assigns(:follows).to_a + expect(assigned.size).to eq 1 + expect(assigned[0]).to eq follow1 + end end context 'when format is json' do diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb index d5e4ee587..e9a1f597d 100644 --- a/spec/controllers/following_accounts_controller_spec.rb +++ b/spec/controllers/following_accounts_controller_spec.rb @@ -22,6 +22,18 @@ describe FollowingAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 end + + it 'does not assign blocked users' do + user = Fabricate(:user) + user.account.block!(followee0) + sign_in(user) + + expect(response).to have_http_status(200) + + assigned = assigns(:follows).to_a + expect(assigned.size).to eq 1 + expect(assigned[0]).to eq follow1 + end end context 'when format is json' do diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb index b874c255b..de1a6de03 100644 --- a/spec/features/log_in_spec.rb +++ b/spec/features/log_in_spec.rb @@ -1,47 +1,51 @@ -require "rails_helper" +# frozen_string_literal: true + +require 'rails_helper' + +feature 'Log in' do + include ProfileStories -feature "Log in" do given(:email) { "test@example.com" } given(:password) { "password" } given(:confirmed_at) { Time.zone.now } background do - Fabricate(:user, email: email, password: password, confirmed_at: confirmed_at) + as_a_registered_user visit new_user_session_path end subject { page } - scenario "A valid email and password user is able to log in" do - fill_in "user_email", with: email - fill_in "user_password", with: password + scenario 'A valid email and password user is able to log in' do + fill_in 'user_email', with: email + fill_in 'user_password', with: password click_on I18n.t('auth.login') - is_expected.to have_css("div.app-holder") + is_expected.to have_css('div.app-holder') end - scenario "A invalid email and password user is not able to log in" do - fill_in "user_email", with: "invalid_email" - fill_in "user_password", with: "invalid_password" + scenario 'A invalid email and password user is not able to log in' do + fill_in 'user_email', with: 'invalid_email' + fill_in 'user_password', with: 'invalid_password' click_on I18n.t('auth.login') - is_expected.to have_css(".flash-message", text: failure_message("invalid")) + is_expected.to have_css('.flash-message', text: failure_message('invalid')) end context do given(:confirmed_at) { nil } - scenario "A unconfirmed user is able to log in" do - fill_in "user_email", with: email - fill_in "user_password", with: password + scenario 'A unconfirmed user is able to log in' do + fill_in 'user_email', with: email + fill_in 'user_password', with: password click_on I18n.t('auth.login') - is_expected.to have_css("div.admin-wrapper") + is_expected.to have_css('div.admin-wrapper') end end def failure_message(message) keys = User.authentication_keys.map { |key| User.human_attribute_name(key) } - I18n.t("devise.failure.#{message}", authentication_keys: keys.join("support.array.words_connector")) + I18n.t("devise.failure.#{message}", authentication_keys: keys.join('support.array.words_connector')) end end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb new file mode 100644 index 000000000..3202167ca --- /dev/null +++ b/spec/features/profile_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +feature 'Profile' do + include ProfileStories + + given(:local_domain) { ENV['LOCAL_DOMAIN'] } + + background do + as_a_logged_in_user + with_alice_as_local_user + end + + subject { page } + + scenario 'I can view Annes public account' do + visit account_path('alice') + + is_expected.to have_title("alice (@alice@#{local_domain})") + + within('.public-account-header h1') do + is_expected.to have_content("alice @alice@#{local_domain}") + end + + bio_elem = first('.public-account-bio') + expect(bio_elem).to have_content(alice_bio) + # The bio has hashtags made clickable + expect(bio_elem).to have_link('cryptology') + expect(bio_elem).to have_link('science') + # Nicknames are make clickable + expect(bio_elem).to have_link('@alice') + expect(bio_elem).to have_link('@bob') + # Nicknames not on server are not clickable + expect(bio_elem).not_to have_link('@pepe') + end + + scenario 'I can change my account' do + visit settings_profile_path + fill_in 'Display name', with: 'Bob' + fill_in 'Bio', with: 'Bob is silent' + click_on 'Save changes' + is_expected.to have_content 'Changes successfully saved!' + + # View my own public profile and see the changes + click_link "Bob @bob@#{local_domain}" + + within('.public-account-header h1') do + is_expected.to have_content("Bob @bob@#{local_domain}") + end + expect(first('.public-account-bio')).to have_content('Bob is silent') + end +end diff --git a/spec/middleware/handle_bad_encoding_middleware_spec.rb b/spec/middleware/handle_bad_encoding_middleware_spec.rb new file mode 100644 index 000000000..8c0d24f18 --- /dev/null +++ b/spec/middleware/handle_bad_encoding_middleware_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe HandleBadEncodingMiddleware do + let(:app) { double() } + let(:middleware) { HandleBadEncodingMiddleware.new(app) } + + it "request with query string is unchanged" do + expect(app).to receive(:call).with("PATH" => "/some/path", "QUERY_STRING" => "name=fred") + middleware.call("PATH" => "/some/path", "QUERY_STRING" => "name=fred") + end + + it "request with no query string is unchanged" do + expect(app).to receive(:call).with("PATH" => "/some/path") + middleware.call("PATH" => "/some/path") + end + + it "request with invalid encoding in query string drops query string" do + expect(app).to receive(:call).with("QUERY_STRING" => "", "PATH" => "/some/path") + middleware.call("QUERY_STRING" => "q=%2Fsearch%2Fall%Forder%3Ddescending%26page%3D5%26sort%3Dcreated_at", "PATH" => "/some/path") + end +end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index b2f6234cb..3cca9b343 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -823,4 +823,5 @@ RSpec.describe Account, type: :model do end include_examples 'AccountAvatar', :account + include_examples 'AccountHeader', :account end diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index 7ddfba7ed..a275621a1 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -133,6 +133,24 @@ RSpec.describe MediaAttachment, type: :model do expect(media.file.meta["small"]["height"]).to eq 327 expect(media.file.meta["small"]["aspect"]).to eq 490.0 / 327 end + + it 'gives the file a random name' do + expect(media.file_file_name).to_not eq 'attachment.jpg' + end + end + + describe 'base64-encoded jpeg' do + let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } + let(:media) { MediaAttachment.create(account: Fabricate(:account), file: base64_attachment) } + + it 'saves media attachment' do + expect(media.persisted?).to be true + expect(media.file).to_not be_nil + end + + it 'gives the file a file name' do + expect(media.file_file_name).to_not be_blank + end end describe 'descriptions for remote attachments' do diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb index b1abd79b0..c30de8eeb 100644 --- a/spec/services/process_mentions_service_spec.rb +++ b/spec/services/process_mentions_service_spec.rb @@ -5,11 +5,11 @@ RSpec.describe ProcessMentionsService, type: :service do let(:visibility) { :public } let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) } + subject { ProcessMentionsService.new } + context 'OStatus with public toot' do let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') } - subject { ProcessMentionsService.new } - before do stub_request(:post, remote_user.salmon_url) subject.call(status) @@ -24,8 +24,6 @@ RSpec.describe ProcessMentionsService, type: :service do let(:visibility) { :private } let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') } - subject { ProcessMentionsService.new } - before do stub_request(:post, remote_user.salmon_url) subject.call(status) @@ -41,29 +39,45 @@ RSpec.describe ProcessMentionsService, type: :service do end context 'ActivityPub' do - let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + context do + let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } - subject { ProcessMentionsService.new } + before do + stub_request(:post, remote_user.inbox_url) + subject.call(status) + end - before do - stub_request(:post, remote_user.inbox_url) - subject.call(status) + it 'creates a mention' do + expect(remote_user.mentions.where(status: status).count).to eq 1 + end + + it 'sends activity to the inbox' do + expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once + end end - it 'creates a mention' do - expect(remote_user.mentions.where(status: status).count).to eq 1 - end + context 'with an IDN domain' do + let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') } + let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") } - it 'sends activity to the inbox' do - expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once + before do + stub_request(:post, remote_user.inbox_url) + subject.call(status) + end + + it 'creates a mention' do + expect(remote_user.mentions.where(status: status).count).to eq 1 + end + + it 'sends activity to the inbox' do + expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once + end end end context 'Temporarily-unreachable ActivityPub user' do let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) } - subject { ProcessMentionsService.new } - before do stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404) stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500) diff --git a/spec/support/examples/models/concerns/account_avatar.rb b/spec/support/examples/models/concerns/account_avatar.rb index f2a8a2459..2180f5273 100644 --- a/spec/support/examples/models/concerns/account_avatar.rb +++ b/spec/support/examples/models/concerns/account_avatar.rb @@ -16,4 +16,24 @@ shared_examples 'AccountAvatar' do |fabricator| end end end + + describe 'base64-encoded files' do + let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } + let(:account) { Fabricate(fabricator, avatar: base64_attachment) } + + it 'saves avatar' do + expect(account.persisted?).to be true + expect(account.avatar).to_not be_nil + end + + it 'gives the avatar a file name' do + expect(account.avatar_file_name).to_not be_blank + end + + it 'saves a new avatar under a different file name' do + previous_file_name = account.avatar_file_name + account.update(avatar: base64_attachment) + expect(account.avatar_file_name).to_not eq previous_file_name + end + end end diff --git a/spec/support/examples/models/concerns/account_header.rb b/spec/support/examples/models/concerns/account_header.rb new file mode 100644 index 000000000..77ee0e629 --- /dev/null +++ b/spec/support/examples/models/concerns/account_header.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +shared_examples 'AccountHeader' do |fabricator| + describe 'base64-encoded files' do + let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } + let(:account) { Fabricate(fabricator, header: base64_attachment) } + + it 'saves header' do + expect(account.persisted?).to be true + expect(account.header).to_not be_nil + end + + it 'gives the header a file name' do + expect(account.header_file_name).to_not be_blank + end + + it 'saves a new header under a different file name' do + previous_file_name = account.header_file_name + account.update(header: base64_attachment) + expect(account.header_file_name).to_not eq previous_file_name + end + end +end diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb new file mode 100644 index 000000000..75b413330 --- /dev/null +++ b/spec/support/stories/profile_stories.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ProfileStories + attr_reader :bob, :alice, :alice_bio + + def as_a_registered_user + @bob = Fabricate( + :user, + email: email, password: password, confirmed_at: confirmed_at, + account: Fabricate(:account, username: 'bob') + ) + end + + def as_a_logged_in_user + as_a_registered_user + visit new_user_session_path + fill_in 'user_email', with: email + fill_in 'user_password', with: password + click_on I18n.t('auth.login') + end + + def with_alice_as_local_user + @alice_bio = '@alice and @bob are fictional characters commonly used as'\ + 'placeholder names in #cryptology, as well as #science and'\ + 'engineering 📖 literature. Not affilated with @pepe.' + + @alice = Fabricate( + :user, + email: 'alice@example.com', password: password, confirmed_at: confirmed_at, + account: Fabricate(:account, username: 'alice', note: @alice_bio) + ) + end + + def confirmed_at + @confirmed_at ||= Time.zone.now + end + + def email + @email ||= 'test@example.com' + end + + def password + @password ||= 'password' + end +end diff --git a/spec/workers/refollow_worker_spec.rb b/spec/workers/refollow_worker_spec.rb new file mode 100644 index 000000000..29771aa59 --- /dev/null +++ b/spec/workers/refollow_worker_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe RefollowWorker do + subject { described_class.new } + let(:account) { Fabricate(:account, domain: 'example.org', protocol: :activitypub) } + let(:alice) { Fabricate(:account, domain: nil, username: 'alice') } + let(:bob) { Fabricate(:account, domain: nil, username: 'bob') } + + describe 'perform' do + let(:service) { double } + + before do + allow(FollowService).to receive(:new).and_return(service) + allow(service).to receive(:call) + + alice.follow!(account, reblogs: true) + bob.follow!(account, reblogs: false) + end + + it 'calls FollowService for local followers' do + result = subject.perform(account.id) + + expect(result).to be_nil + expect(service).to have_received(:call).with(alice, account, reblogs: true) + expect(service).to have_received(:call).with(bob, account, reblogs: false) + end + end +end diff --git a/yarn.lock b/yarn.lock index f7834e469..258567ddb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,15 +9,15 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.5": - version "7.7.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e" - integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw== +"@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.7": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.7.tgz#ee155d2e12300bcc0cff6a8ad46f2af5063803e9" + integrity sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.7.4" + "@babel/generator" "^7.7.7" "@babel/helpers" "^7.7.4" - "@babel/parser" "^7.7.5" + "@babel/parser" "^7.7.7" "@babel/template" "^7.7.4" "@babel/traverse" "^7.7.4" "@babel/types" "^7.7.4" @@ -29,10 +29,10 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0", "@babel/generator@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.4.tgz#db651e2840ca9aa66f327dcec1dc5f5fa9611369" - integrity sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg== +"@babel/generator@^7.0.0", "@babel/generator@^7.7.4", "@babel/generator@^7.7.7": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.7.tgz#859ac733c44c74148e1a72980a64ec84b85f4f45" + integrity sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ== dependencies: "@babel/types" "^7.7.4" jsesc "^2.5.1" @@ -252,10 +252,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5": - version "7.7.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71" - integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.7": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.7.tgz#1b886595419cf92d811316d5b715a53ff38b4937" + integrity sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw== "@babel/plugin-proposal-async-generator-functions@^7.7.4": version "7.7.4" @@ -795,10 +795,10 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@clusterws/cws@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.0.tgz#f6116cbf3a8b7ad0657916616ce5f8248746b797" - integrity sha512-YeGpAPIdkBsOnAkmFKVMWEjCKDH900U2if0B+nc1imfv+64AIb2JX2xiTA6BLDLppEgWV5c6bpWESjbHCNblHw== +"@clusterws/cws@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.1.tgz#443c47909601e87b881ffb2c4c83cbf27a228487" + integrity sha512-OamMsXwVppfmwX14Ed1msJJN0KNi+VBQh5AkAqUvIGTcJyHcjpsKjU15wS8QumvEewxx9gvGp7aSDop0/R5Gqg== "@cnakazawa/watch@^1.0.3": version "1.0.3" @@ -3247,7 +3247,7 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" -define-properties@^1.1.1, define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -3942,10 +3942,10 @@ eslint@^2.7.0: text-table "~0.2.0" user-home "^2.0.0" -eslint@^6.7.2: - version "6.7.2" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.7.2.tgz#c17707ca4ad7b2d8af986a33feba71e18a9fecd1" - integrity sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng== +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -4031,12 +4031,7 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -esutils@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= - -esutils@^2.0.2: +esutils@^2.0.0, esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -4385,10 +4380,10 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.0.0, find-cache-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.1.0.tgz#9935894999debef4cf9f677fdf646d002c4cdecb" - integrity sha512-zw+EFiNBNPgI2NTrKkDd1xd7q0cs6wr/iWnr/oUkI0yF9K9GqQ+riIt4aiyFaaqpaWbxPrJXHI+QvmNUQbX+0Q== +find-cache-dir@^3.0.0, find-cache-dir@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874" + integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg== dependencies: commondir "^1.0.1" make-dir "^3.0.0" @@ -5620,12 +5615,12 @@ is-my-json-valid@^2.10.0: jsonpointer "^4.0.0" xtend "^4.0.0" -is-nan@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" - integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= +is-nan@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03" + integrity sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ== dependencies: - define-properties "^1.1.1" + define-properties "^1.1.3" is-number-object@^1.0.4: version "1.0.4" @@ -8785,15 +8780,15 @@ react-swipeable-views@^0.13.3: react-swipeable-views-utils "^0.13.3" warning "^4.0.1" -react-test-renderer@^16.0.0-0, react-test-renderer@^16.11.0: - version "16.11.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8" - integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag== +react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.12.0.tgz#11417ffda579306d4e841a794d32140f3da1b43f" + integrity sha512-Vj/teSqt2oayaWxkbhQ6gKis+t5JrknXfPVo+aIJ8QwYAqMPH77uptOdrlphyxl8eQI/rtkOYg86i/UWkpFu0w== dependencies: object-assign "^4.1.1" prop-types "^15.6.2" react-is "^16.8.6" - scheduler "^0.17.0" + scheduler "^0.18.0" react-textarea-autosize@^7.1.2: version "7.1.2" @@ -8946,10 +8941,10 @@ redux-thunk@^2.2.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== -redux@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" - integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== +redux@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== dependencies: loose-envify "^1.4.0" symbol-observable "^1.2.0" @@ -9395,14 +9390,6 @@ sax@^1.2.4, sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe" - integrity sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" @@ -9487,12 +9474,7 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-javascript@^1.7.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" - integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A== - -serialize-javascript@^2.1.1, serialize-javascript@^2.1.2: +serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== @@ -10164,39 +10146,39 @@ tcomb@^2.5.0: resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" integrity sha1-ENYpWAQWaaXVNWe5pO6M3iKxwrA= -terser-webpack-plugin@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" - integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== +terser-webpack-plugin@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== dependencies: cacache "^12.0.2" find-cache-dir "^2.1.0" is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^1.7.0" + serialize-javascript "^2.1.2" source-map "^0.6.1" terser "^4.1.2" webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser-webpack-plugin@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.2.2.tgz#2a6e00237125564a455ad69b22e08ee59420473a" - integrity sha512-/CHMNswPMAwuD2kd++qys8UmBRmsshPSzHw4BlDwurPtK9YjeK93OV89YWkJulHk972cs07K/7Z92V6PNjWF8A== +terser-webpack-plugin@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.1.tgz#6a63c27debc15b25ffd2588562ee2eeabdcab923" + integrity sha512-dNxivOXmDgZqrGxOttBH6B4xaxT4zNC+Xd+2K8jwGDMK5q2CZI+KZMA1AAnSRT+BTRvuzKsDx+fpxzPAmAMVcA== dependencies: cacache "^13.0.1" - find-cache-dir "^3.1.0" + find-cache-dir "^3.2.0" jest-worker "^24.9.0" schema-utils "^2.6.1" - serialize-javascript "^2.1.1" + serialize-javascript "^2.1.2" source-map "^0.6.1" - terser "^4.4.2" + terser "^4.4.3" webpack-sources "^1.4.3" -terser@^4.1.2, terser@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.2.tgz#448fffad0245f4c8a277ce89788b458bfd7706e8" - integrity sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ== +terser@^4.1.2, terser@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.3.tgz#401abc52b88869cf904412503b1eb7da093ae2f0" + integrity sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -10858,10 +10840,10 @@ webpack-sources@^1.0.0, webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack- source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.41.2: - version "4.41.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e" - integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A== +webpack@^4.41.5: + version "4.41.5" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.5.tgz#3210f1886bce5310e62bb97204d18c263341b77c" + integrity sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw== dependencies: "@webassemblyjs/ast" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5" @@ -10883,7 +10865,7 @@ webpack@^4.41.2: node-libs-browser "^2.2.1" schema-utils "^1.0.0" tapable "^1.1.3" - terser-webpack-plugin "^1.4.1" + terser-webpack-plugin "^1.4.3" watchpack "^1.6.0" webpack-sources "^1.4.1"