Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `app/controllers/application_controller.rb`:
  Conflict due to theming system.
- `app/controllers/oauth/authorizations_controller.rb`:
  Conflict due to theming system.
signup-info-prompt
Thibaut Girka 2020-01-04 22:54:06 +01:00
commit 01eaeab56d
72 changed files with 708 additions and 376 deletions

View File

@ -3,7 +3,7 @@ version: 2
aliases: aliases:
- &defaults - &defaults
docker: docker:
- image: circleci/ruby:2.6-stretch-node - image: circleci/ruby:2.6-buster-node
environment: &ruby_environment environment: &ruby_environment
BUNDLE_APP_CONFIG: ./.bundle/ BUNDLE_APP_CONFIG: ./.bundle/
DB_HOST: localhost DB_HOST: localhost
@ -39,7 +39,6 @@ aliases:
steps: steps:
- checkout - checkout
- *attach_workspace - *attach_workspace
- restore_cache: - restore_cache:
keys: keys:
- v1-node-dependencies-{{ checksum "yarn.lock" }} - v1-node-dependencies-{{ checksum "yarn.lock" }}
@ -49,7 +48,6 @@ aliases:
key: v1-node-dependencies-{{ checksum "yarn.lock" }} key: v1-node-dependencies-{{ checksum "yarn.lock" }}
paths: paths:
- ./node_modules/ - ./node_modules/
- *persist_to_workspace - *persist_to_workspace
- &install_system_dependencies - &install_system_dependencies
@ -59,12 +57,16 @@ aliases:
sudo apt-get update sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler 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 - &install_ruby_dependencies
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
- *restore_ruby_dependencies - *restore_ruby_dependencies
- run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production && bundle clean - 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 - &test_steps
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: sudo apt-get install -y ffmpeg - run: sudo apt-get install -y ffmpeg
- run: - run:
name: Prepare Tests name: Prepare Tests
command: ./bin/rails parallel:create parallel:load_schema parallel:prepare command: ./bin/rails parallel:create parallel:load_schema parallel:prepare
@ -105,14 +105,14 @@ jobs:
install-ruby2.5: install-ruby2.5:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.5-stretch-node - image: circleci/ruby:2.5-buster-node
environment: *ruby_environment environment: *ruby_environment
<<: *install_ruby_dependencies <<: *install_ruby_dependencies
install-ruby2.4: install-ruby2.4:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.4-stretch-node - image: circleci/ruby:2.4-buster-node
environment: *ruby_environment environment: *ruby_environment
<<: *install_ruby_dependencies <<: *install_ruby_dependencies
@ -131,7 +131,7 @@ jobs:
test-ruby2.6: test-ruby2.6:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.6-stretch-node - image: circleci/ruby:2.6-buster-node
environment: *ruby_environment environment: *ruby_environment
- image: circleci/postgres:10.6-alpine - image: circleci/postgres:10.6-alpine
environment: environment:
@ -142,7 +142,7 @@ jobs:
test-ruby2.5: test-ruby2.5:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.5-stretch-node - image: circleci/ruby:2.5-buster-node
environment: *ruby_environment environment: *ruby_environment
- image: circleci/postgres:10.6-alpine - image: circleci/postgres:10.6-alpine
environment: environment:
@ -153,7 +153,7 @@ jobs:
test-ruby2.4: test-ruby2.4:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.4-stretch-node - image: circleci/ruby:2.4-buster-node
environment: *ruby_environment environment: *ruby_environment
- image: circleci/postgres:10.6-alpine - image: circleci/postgres:10.6-alpine
environment: environment:
@ -164,7 +164,7 @@ jobs:
test-webui: test-webui:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/node:12.9-stretch - image: circleci/node:12-buster
steps: steps:
- *attach_workspace - *attach_workspace
- run: ./bin/retry yarn test:jest - run: ./bin/retry yarn test:jest

View File

@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep
SHELL ["bash", "-c"] SHELL ["bash", "-c"]
# Install Node v12 (LTS) # Install Node v12 (LTS)
ENV NODE_VER="12.13.1" ENV NODE_VER="12.14.0"
RUN echo "Etc/UTC" > /etc/localtime && \ RUN echo "Etc/UTC" > /etc/localtime && \
apt update && \ apt update && \
apt -y install wget python && \ apt -y install wget python && \

14
Gemfile
View File

@ -7,11 +7,11 @@ gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 4.3' gem 'puma', '~> 4.3'
gem 'rails', '~> 5.2.4' gem 'rails', '~> 5.2.4'
gem 'sprockets', '~> 3.7' gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 0.20' gem 'thor', '~> 0.20'
gem 'hamlit-rails', '~> 0.2' gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.1' gem 'pg', '~> 1.2'
gem 'makara', '~> 0.4' gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.4' gem 'pghero', '~> 2.4'
gem 'dotenv-rails', '~> 2.7' gem 'dotenv-rails', '~> 2.7'
@ -31,7 +31,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.7' gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639' gem 'iso-639'
gem 'chewy', '~> 5.1' gem 'chewy', '~> 5.1'
gem 'cld3', '~> 3.2.4' gem 'cld3', '~> 3.2.6'
gem 'devise', '~> 4.7' gem 'devise', '~> 4.7'
gem 'devise-two-factor', '~> 3.1' gem 'devise-two-factor', '~> 3.1'
@ -50,7 +50,7 @@ gem 'fast_blank', '~> 1.0'
gem 'fastimage' gem 'fastimage'
gem 'goldfinger', '~> 2.1' gem 'goldfinger', '~> 2.1'
gem 'hiredis', '~> 0.6' 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 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
gem 'html2text' gem 'html2text'
gem 'htmlentities', '~> 4.3' gem 'htmlentities', '~> 4.3'
@ -61,7 +61,7 @@ gem 'httplog', '~> 1.3'
gem 'idn-ruby', require: 'idn' gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1' gem 'kaminari', '~> 1.1'
gem 'link_header', '~> 0.0' 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 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
gem 'nokogiri', '~> 1.10' gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2' gem 'nsa', '~> 0.2'
@ -120,8 +120,8 @@ end
group :test do group :test do
gem 'capybara', '~> 3.29' gem 'capybara', '~> 3.29'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.9' gem 'faker', '~> 2.10'
gem 'microformats', '~> 4.1' gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0' gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0' gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.17', require: false gem 'simplecov', '~> 0.17', require: false

View File

@ -185,10 +185,10 @@ GEM
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.5) crass (1.0.5)
css_parser (1.7.0) css_parser (1.7.1)
addressable addressable
debug_inspector (0.0.3) debug_inspector (0.0.3)
derailed_benchmarks (1.4.2) derailed_benchmarks (1.4.3)
benchmark-ips (~> 2) benchmark-ips (~> 2)
get_process_mem (~> 0) get_process_mem (~> 0)
heapy (~> 0) heapy (~> 0)
@ -240,9 +240,9 @@ GEM
tzinfo tzinfo
excon (0.71.0) excon (0.71.0)
fabrication (2.21.0) fabrication (2.21.0)
faker (2.9.0) faker (2.10.0)
i18n (>= 1.6, < 1.8) i18n (>= 1.6, < 1.8)
faraday (0.15.4) faraday (1.0.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
fast_blank (1.0.0) fast_blank (1.0.0)
fastimage (2.1.7) fastimage (2.1.7)
@ -275,8 +275,8 @@ GEM
http (~> 3.0) http (~> 3.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
oj (~> 3.0) oj (~> 3.0)
hamlit (2.9.3) hamlit (2.11.0)
temple (>= 0.8.0) temple (>= 0.8.2)
thor thor
tilt tilt
hamlit-rails (0.2.3) hamlit-rails (0.2.3)
@ -374,9 +374,9 @@ GEM
microformats (4.1.0) microformats (4.1.0)
json (~> 2.1) json (~> 2.1)
nokogiri (~> 1.8, >= 1.8.3) nokogiri (~> 1.8, >= 1.8.3)
mime-types (3.3) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2019.0904) mime-types-data (3.2019.1009)
mimemagic (0.3.3) mimemagic (0.3.3)
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
@ -434,7 +434,7 @@ GEM
pastel (0.7.3) pastel (0.7.3)
equatable (~> 0.6) equatable (~> 0.6)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.1.4) pg (1.2.0)
pghero (2.4.1) pghero (2.4.1)
activerecord (>= 5) activerecord (>= 5)
pkg-config (1.4.0) pkg-config (1.4.0)
@ -454,7 +454,7 @@ GEM
pry (~> 0.10) pry (~> 0.10)
pry-rails (0.3.9) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (4.0.1) public_suffix (4.0.2)
puma (4.3.1) puma (4.3.1)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.0)
@ -463,7 +463,7 @@ GEM
rack (2.0.8) rack (2.0.8)
rack-attack (6.2.2) rack-attack (6.2.2)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-cors (1.1.0) rack-cors (1.1.1)
rack (>= 2.0.0) rack (>= 2.0.0)
rack-protection (2.0.7) rack-protection (2.0.7)
rack rack
@ -520,7 +520,7 @@ GEM
redis-activesupport (5.0.4) redis-activesupport (5.0.4)
activesupport (>= 3, < 6) activesupport (>= 3, < 6)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-namespace (1.6.0) redis-namespace (1.7.0)
redis (>= 3.0.4) redis (>= 3.0.4)
redis-rack (2.0.4) redis-rack (2.0.4)
rack (>= 1.5, < 3) rack (>= 1.5, < 3)
@ -532,7 +532,7 @@ GEM
redis-store (1.5.0) redis-store (1.5.0)
redis (>= 2.2, < 5) redis (>= 2.2, < 5)
regexp_parser (1.6.0) regexp_parser (1.6.0)
request_store (1.4.1) request_store (1.5.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.0.0) responders (3.0.0)
actionpack (>= 5.0) actionpack (>= 5.0)
@ -618,21 +618,21 @@ GEM
sshkit (1.20.0) sshkit (1.20.0)
net-scp (>= 1.1.2) net-scp (>= 1.1.2)
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
stackprof (0.2.14) stackprof (0.2.15)
statsd-ruby (1.4.0) statsd-ruby (1.4.0)
stoplight (2.2.0) stoplight (2.2.0)
streamio-ffmpeg (3.0.2) streamio-ffmpeg (3.0.2)
multi_json (~> 1.8) multi_json (~> 1.8)
strong_migrations (0.5.1) strong_migrations (0.5.1)
activerecord (>= 5) activerecord (>= 5)
temple (0.8.1) temple (0.8.2)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
terrapin (0.6.0) terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0) climate_control (>= 0.0.3, < 1.0)
thor (0.20.3) thor (0.20.3)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.9) tilt (2.0.10)
tty-color (0.5.0) tty-color (0.5.0)
tty-command (0.9.0) tty-command (0.9.0)
pastel (~> 0.7.0) pastel (~> 0.7.0)
@ -654,7 +654,7 @@ GEM
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.5) unf_ext (0.0.7.6)
unicode-display_width (1.6.0) unicode-display_width (1.6.0)
uniform_notifier (1.12.1) uniform_notifier (1.12.1)
warden (1.2.8) warden (1.2.8)
@ -713,7 +713,7 @@ DEPENDENCIES
doorkeeper (~> 5.2) doorkeeper (~> 5.2)
dotenv-rails (~> 2.7) dotenv-rails (~> 2.7)
fabrication (~> 2.21) fabrication (~> 2.21)
faker (~> 2.9) faker (~> 2.10)
fast_blank (~> 1.0) fast_blank (~> 1.0)
fastimage fastimage
fog-core (<= 2.1.0) fog-core (<= 2.1.0)
@ -759,7 +759,7 @@ DEPENDENCIES
parallel (~> 1.19) parallel (~> 1.19)
parallel_tests (~> 2.30) parallel_tests (~> 2.30)
parslet parslet
pg (~> 1.1) pg (~> 1.2)
pghero (~> 2.4) pghero (~> 2.4)
pkg-config (~> 1.4) pkg-config (~> 1.4)
posix-spawn! posix-spawn!
@ -778,7 +778,7 @@ DEPENDENCIES
rdf-normalize (~> 0.3) rdf-normalize (~> 0.3)
redcarpet (~> 3.4) redcarpet (~> 3.4)
redis (~> 4.1) redis (~> 4.1)
redis-namespace (~> 1.5) redis-namespace (~> 1.7)
redis-rails (~> 5.0) redis-rails (~> 5.0)
rqrcode (~> 0.10) rqrcode (~> 0.10)
rspec-rails (~> 3.9) rspec-rails (~> 3.9)
@ -808,9 +808,3 @@ DEPENDENCIES
webmock (~> 3.7) webmock (~> 3.7)
webpacker (~> 4.2) webpacker (~> 4.2)
webpush webpush
RUBY VERSION
ruby 2.6.5p114
BUNDLED WITH
1.17.3

View File

@ -2,10 +2,6 @@
module Admin module Admin
class CustomEmojisController < BaseController class CustomEmojisController < BaseController
include ObfuscateFilename
obfuscate_filename [:custom_emoji, :image]
def index def index
authorize :custom_emoji, :index? authorize :custom_emoji, :index?

View File

@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
def load_accounts def load_accounts
return [] if hide_results? 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 end
def hide_results? 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 end
def default_accounts def default_accounts

View File

@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
def load_accounts def load_accounts
return [] if hide_results? 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 end
def hide_results? 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 end
def default_accounts def default_accounts

View File

@ -4,9 +4,6 @@ class Api::V1::MediaController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action -> { doorkeeper_authorize! :write, :'write:media' }
before_action :require_user! before_action :require_user!
include ObfuscateFilename
obfuscate_filename :file
respond_to :json respond_to :json
def create def create

View File

@ -17,7 +17,9 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
private private
def load_accounts 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 end
def default_accounts def default_accounts

View File

@ -17,7 +17,9 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
private private
def load_accounts 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 end
def default_accounts def default_accounts

View File

@ -25,6 +25,7 @@ class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from ActionController::UnknownFormat, with: :not_acceptable rescue_from ActionController::UnknownFormat, with: :not_acceptable
rescue_from ActionController::ParameterMissing, with: :bad_request rescue_from ActionController::ParameterMissing, with: :bad_request
rescue_from Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
@ -211,7 +212,12 @@ class ApplicationController < ActionController::Base
end end
def respond_with_error(code) def respond_with_error(code)
respond_to do |format|
format.any do
use_pack 'error' use_pack 'error'
render "errors/#{code}", layout: 'error', status: code, formats: [:html] render "errors/#{code}", layout: 'error', status: code, formats: [:html]
end end
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
end
end
end end

View File

@ -11,6 +11,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create, :edit, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update] before_action :require_not_suspended!, only: [:update]
before_action :set_cache_headers, only: [:edit, :update]
skip_before_action :require_functional!, only: [:edit, :update] skip_before_action :require_functional!, only: [:edit, :update]
@ -114,4 +115,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def require_not_suspended! def require_not_suspended!
forbidden if current_account.suspended? forbidden if current_account.suspended?
end end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end
end end

View File

@ -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

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class FiltersController < ApplicationController class FiltersController < ApplicationController
include Authorization
layout 'admin' layout 'admin'
before_action :authenticate_user!
before_action :set_filters, only: :index before_action :set_filters, only: :index
before_action :set_filter, only: [:edit, :update, :destroy] before_action :set_filter, only: [:edit, :update, :destroy]
before_action :set_pack before_action :set_pack

View File

@ -19,7 +19,6 @@ class FollowerAccountsController < ApplicationController
next if @account.user_hides_network? next if @account.user_hides_network?
follows follows
@relationships = AccountRelationshipsPresenter.new(follows.map(&:account_id), current_user.account_id) if user_signed_in?
end end
format.json do format.json do
@ -38,7 +37,11 @@ class FollowerAccountsController < ApplicationController
private private
def follows 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 end
def page_requested? def page_requested?

View File

@ -19,7 +19,6 @@ class FollowingAccountsController < ApplicationController
next if @account.user_hides_network? next if @account.user_hides_network?
follows follows
@relationships = AccountRelationshipsPresenter.new(follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
end end
format.json do format.json do
@ -38,7 +37,11 @@ class FollowingAccountsController < ApplicationController
private private
def follows 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 end
def page_requested? def page_requested?

View File

@ -6,6 +6,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_action :store_current_location before_action :store_current_location
before_action :authenticate_resource_owner! before_action :authenticate_resource_owner!
before_action :set_pack before_action :set_pack
before_action :set_cache_headers
include Localized include Localized
@ -32,4 +33,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
def truthy_param?(key) def truthy_param?(key)
ActiveModel::Type::Boolean.new.cast(params[key]) ActiveModel::Type::Boolean.new.cast(params[key])
end end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end
end end

View File

@ -3,6 +3,7 @@
class Settings::BaseController < ApplicationController class Settings::BaseController < ApplicationController
before_action :set_pack before_action :set_pack
before_action :set_body_classes before_action :set_body_classes
before_action :set_cache_headers
private private
@ -13,4 +14,8 @@ class Settings::BaseController < ApplicationController
def set_body_classes def set_body_classes
@body_classes = 'admin' @body_classes = 'admin'
end end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end
end end

View File

@ -1,16 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ProfilesController < Settings::BaseController class Settings::ProfilesController < Settings::BaseController
include ObfuscateFilename
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_account before_action :set_account
obfuscate_filename [:account, :avatar]
obfuscate_filename [:account, :header]
def show def show
@account.build_fields @account.build_fields
end end

View File

@ -8,12 +8,8 @@ module WellKnown
def show def show
@webfinger_template = "#{webfinger_url}?resource={uri}" @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 expires_in 3.days, public: true
render content_type: 'application/xrd+xml', formats: [:xml]
end end
end end
end end

View File

@ -13,7 +13,7 @@ module AccountsHelper
if account.local? if account.local?
"@#{account.acct}@#{Rails.configuration.x.local_domain}" "@#{account.acct}@#{Rails.configuration.x.local_domain}"
else else
"@#{account.acct}" "@#{account.pretty_acct}"
end end
end end

View File

@ -28,6 +28,7 @@ export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
export const STATUS_REVEAL = 'STATUS_REVEAL'; export const STATUS_REVEAL = 'STATUS_REVEAL';
export const STATUS_HIDE = 'STATUS_HIDE'; export const STATUS_HIDE = 'STATUS_HIDE';
export const STATUS_COLLAPSE = 'STATUS_COLLAPSE';
export const REDRAFT = 'REDRAFT'; export const REDRAFT = 'REDRAFT';
@ -320,3 +321,11 @@ export function revealStatus(ids) {
ids, ids,
}; };
}; };
export function toggleStatusCollapse(id, isCollapsed) {
return {
type: STATUS_COLLAPSE,
id,
isCollapsed,
};
}

View File

@ -208,10 +208,13 @@ export default class ScrollableList extends PureComponent {
} }
attachIntersectionObserver () { attachIntersectionObserver () {
this.intersectionObserverWrapper.connect({ let nodeOptions = {
root: this.node, root: this.node,
rootMargin: '300% 0px', rootMargin: '300% 0px',
}); };
this.intersectionObserverWrapper
.connect(this.props.bindToDocument ? {} : nodeOptions);
} }
detachIntersectionObserver () { detachIntersectionObserver () {

View File

@ -76,6 +76,7 @@ class Status extends ImmutablePureComponent {
onEmbed: PropTypes.func, onEmbed: PropTypes.func,
onHeightChange: PropTypes.func, onHeightChange: PropTypes.func,
onToggleHidden: PropTypes.func, onToggleHidden: PropTypes.func,
onToggleCollapsed: PropTypes.func,
muted: PropTypes.bool, muted: PropTypes.bool,
hidden: PropTypes.bool, hidden: PropTypes.bool,
unread: PropTypes.bool, unread: PropTypes.bool,
@ -102,19 +103,6 @@ class Status extends ImmutablePureComponent {
statusId: undefined, 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) { static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) { if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return { 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 = () => { handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia }); this.setState({ showMedia: !this.state.showMedia });
} }
@ -196,7 +158,11 @@ class Status extends ImmutablePureComponent {
handleExpandedToggle = () => { handleExpandedToggle = () => {
this.props.onToggleHidden(this._properStatus()); this.props.onToggleHidden(this._properStatus());
}; }
handleCollapsedToggle = isCollapsed => {
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
}
renderLoadingMediaGallery () { renderLoadingMediaGallery () {
return <div className='media-gallery' style={{ height: '110px' }} />; return <div className='media-gallery' style={{ height: '110px' }} />;
@ -466,7 +432,7 @@ class Status extends ImmutablePureComponent {
</a> </a>
</div> </div>
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable /> <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
{media} {media}

View File

@ -23,11 +23,11 @@ export default class StatusContent extends React.PureComponent {
onExpandedToggle: PropTypes.func, onExpandedToggle: PropTypes.func,
onClick: PropTypes.func, onClick: PropTypes.func,
collapsable: PropTypes.bool, collapsable: PropTypes.bool,
onCollapsedToggle: PropTypes.func,
}; };
state = { state = {
hidden: true, 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 () { _updateStatusLinks () {
@ -62,14 +62,16 @@ export default class StatusContent extends React.PureComponent {
link.setAttribute('rel', 'noopener noreferrer'); link.setAttribute('rel', 'noopener noreferrer');
} }
if ( if (this.props.status.get('collapsed', null) === null) {
let collapsed =
this.props.collapsable this.props.collapsable
&& this.props.onClick && this.props.onClick
&& this.state.collapsed === null
&& node.clientHeight > MAX_HEIGHT && node.clientHeight > MAX_HEIGHT
&& this.props.status.get('spoiler_text').length === 0 && this.props.status.get('spoiler_text').length === 0;
) {
this.setState({ collapsed: true }); 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 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 content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') }; const spoilerContent = { __html: status.get('spoilerHtml') };
@ -185,7 +188,7 @@ export default class StatusContent extends React.PureComponent {
const classNames = classnames('status__content', { const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router, 'status__content--with-action': this.props.onClick && this.context.router,
'status__content--with-spoiler': status.get('spoiler_text').length > 0, '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'))) { if (isRtl(status.get('search_index'))) {
@ -237,7 +240,7 @@ export default class StatusContent extends React.PureComponent {
</div>, </div>,
]; ];
if (this.state.collapsed) { if (renderReadMore) {
output.push(readMoreButton); output.push(readMoreButton);
} }

View File

@ -23,6 +23,7 @@ import {
deleteStatus, deleteStatus,
hideStatus, hideStatus,
revealStatus, revealStatus,
toggleStatusCollapse,
} from '../actions/statuses'; } from '../actions/statuses';
import { import {
unmuteAccount, unmuteAccount,
@ -190,6 +191,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
} }
}, },
onToggleCollapsed (status, isCollapsed) {
dispatch(toggleStatusCollapse(status.get('id'), isCollapsed));
},
onBlockDomain (domain) { onBlockDomain (domain) {
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />, message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,

View File

@ -12,6 +12,7 @@ import {
STATUS_UNMUTE_SUCCESS, STATUS_UNMUTE_SUCCESS,
STATUS_REVEAL, STATUS_REVEAL,
STATUS_HIDE, STATUS_HIDE,
STATUS_COLLAPSE,
} from '../actions/statuses'; } from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines'; import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; 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: case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references); return deleteStatus(state, action.id, action.references);
default: default:

View File

@ -4,9 +4,13 @@ import { FormattedNumber } from 'react-intl';
export const shortNumberFormat = number => { export const shortNumberFormat = number => {
if (number < 1000) { if (number < 1000) {
return <FormattedNumber value={number} />; return <FormattedNumber value={number} />;
} else if (number < 1000000) { } else if (number < 10000) {
return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>; return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>;
} else { } else if (number < 1000000) {
return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={0} />K</Fragment>;
} else if (number < 10000000) {
return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={1} />M</Fragment>; return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={1} />M</Fragment>;
} else {
return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={0} />M</Fragment>;
} }
}; };

View File

@ -6399,13 +6399,13 @@ noscript {
&__links { &__links {
font-size: 14px; font-size: 14px;
color: $darker-text-color; color: $darker-text-color;
padding: 10px 0;
a { a {
display: inline-block; display: inline-block;
color: $darker-text-color; color: $darker-text-color;
text-decoration: none; text-decoration: none;
padding: 10px; padding: 5px 10px;
padding-top: 20px;
font-weight: 500; font-weight: 500;
strong { strong {

View File

@ -78,7 +78,7 @@ class SearchQueryTransformer < Parslet::Transform
elsif clause[:shortcode] elsif clause[:shortcode]
TermClause.new(prefix, operator, ":#{clause[:term]}:") TermClause.new(prefix, operator, ":#{clause[:term]}:")
elsif clause[:phrase] 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 else
raise "Unexpected clause type: #{clause}" raise "Unexpected clause type: #{clause}"
end end

View File

@ -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

View File

@ -50,7 +50,7 @@
class Account < ApplicationRecord class Account < ApplicationRecord
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i 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 AccountAssociations
include AccountAvatar include AccountAvatar
@ -168,6 +168,10 @@ class Account < ApplicationRecord
local? ? username : "#{username}@#{domain}" local? ? username : "#{username}@#{domain}"
end end
def pretty_acct
local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}"
end
def local_username_and_domain def local_username_and_domain
"#{username}@#{Rails.configuration.x.local_domain}" "#{username}@#{Rails.configuration.x.local_domain}"
end end

View File

@ -9,6 +9,7 @@ module Attachmentable
GIF_MATRIX_LIMIT = 921_600 # 1280x720px GIF_MATRIX_LIMIT = 921_600 # 1280x720px
included do included do
before_post_process :obfuscate_file_name
before_post_process :set_file_extensions before_post_process :set_file_extensions
before_post_process :check_image_dimensions before_post_process :check_image_dimensions
before_post_process :set_file_content_type before_post_process :set_file_content_type
@ -68,4 +69,14 @@ module Attachmentable
rescue Terrapin::CommandLineError rescue Terrapin::CommandLineError
'' ''
end 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 end

View File

@ -202,9 +202,12 @@ class MediaAttachment < ApplicationRecord
end end
after_commit :reset_parent_cache, on: :update after_commit :reset_parent_cache, on: :update
before_create :prepare_description, unless: :local? before_create :prepare_description, unless: :local?
before_create :set_shortcode before_create :set_shortcode
before_post_process :set_type_and_extension before_post_process :set_type_and_extension
before_save :set_meta before_save :set_meta
class << self class << self

View File

@ -24,6 +24,10 @@ class REST::AccountSerializer < ActiveModel::Serializer
object.id.to_s object.id.to_s
end end
def acct
object.pretty_acct
end
def note def note
Formatter.instance.simplified_format(object) Formatter.instance.simplified_format(object)
end end

View File

@ -166,7 +166,7 @@ class BackupService < BaseService
io.write(buffer) io.write(buffer)
end end
end end
rescue Errno::ENOENT rescue Errno::ENOENT, Seahorse::Client::NetworkingError
Rails.logger.warn "Could not backup file #{filename}: file not found" Rails.logger.warn "Could not backup file #{filename}: file not found"
end end
end end

View File

@ -15,6 +15,15 @@ class ProcessMentionsService < BaseService
status.text = status.text.gsub(Account::MENTION_RE) do |match| 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) mentioned_account = Account.find_remote(username, domain)
if mention_undeliverable?(mentioned_account) if mention_undeliverable?(mentioned_account)

View File

@ -12,6 +12,8 @@ class ResolveURLService < BaseService
process_local_url process_local_url
elsif !fetched_resource.nil? elsif !fetched_resource.nil?
process_url process_url
elsif @on_behalf_of.present?
process_url_from_db
end end
end end
@ -24,13 +26,17 @@ class ResolveURLService < BaseService
status = FetchRemoteStatusService.new.call(resource_url, body) status = FetchRemoteStatusService.new.call(resource_url, body)
authorize_with @on_behalf_of, status, :show? unless status.nil? authorize_with @on_behalf_of, status, :show? unless status.nil?
status status
elsif fetched_resource.nil? && @on_behalf_of.present? end
end
def process_url_from_db
# It may happen that the resource is a private toot, and thus not fetchable, # 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. # but we can return the toot if we already know about it.
status = Status.find_by(uri: @url) || Status.find_by(url: @url) status = Status.find_by(uri: @url) || Status.find_by(url: @url)
authorize_with @on_behalf_of, status, :show? unless status.nil? authorize_with @on_behalf_of, status, :show? unless status.nil?
status status
end rescue Mastodon::NotPermittedError
nil
end end
def fetched_resource def fetched_resource

View File

@ -1,4 +1,5 @@
.batch-table__row .batch-table__row
- if batch_available
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
= f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id = f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id

View File

@ -47,25 +47,26 @@
.batch-table.optional .batch-table.optional
.batch-table__toolbar .batch-table__toolbar
- if params[:pending_review] == '1' || params[:unreviewed] == '1'
%label.batch-table__toolbar__select.batch-checkbox-all %label.batch-table__toolbar__select.batch-checkbox-all
= check_box_tag :batch_checkbox_all, nil, false = check_box_tag :batch_checkbox_all, nil, false
.batch-table__toolbar__actions .batch-table__toolbar__actions
- if params[:pending_review] == '1'
= 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('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') } = 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') %span.neutral-hint= t('generic.no_batch_actions_available')
.batch-table__body .batch-table__body
- if @tags.empty? - if @tags.empty?
= nothing_here 'nothing-here--under-tabs' = nothing_here 'nothing-here--under-tabs'
- else - 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 = paginate @tags
- if params[:pending_review] == '1' - if params[:pending_review] == '1' || params[:unreviewed] == '1'
%hr.spacer/ %hr.spacer/
%div{ style: 'overflow: hidden' } %div{ style: 'overflow: hidden' }

View File

@ -5,6 +5,10 @@
.fields-group .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 = 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' %h4= t 'appearance.advanced_web_interface'
%p.hint= t 'appearance.advanced_web_interface_hint' %p.hint= t 'appearance.advanced_web_interface_hint'

View File

@ -1,3 +1,2 @@
- cache @status do
.activity-stream.activity-stream--headless .activity-stream.activity-stream--headless
= render 'status', status: @status, centered: true, autoplay: @autoplay = render 'status', status: @status, centered: true, autoplay: @autoplay

View File

@ -7,15 +7,18 @@ class RefollowWorker
def perform(target_account_id) def perform(target_account_id)
target_account = Account.find(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 # Locally unfollow remote account
follower = follow.account
follower.unfollow!(target_account) follower.unfollow!(target_account)
# Schedule re-follow # Schedule re-follow
begin 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 rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
next next
end end

View File

@ -7,6 +7,7 @@ require 'rails/all'
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)
require_relative '../app/lib/exceptions' require_relative '../app/lib/exceptions'
require_relative '../app/middleware/handle_bad_encoding_middleware'
require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/lazy_thumbnail'
require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/gif_transcoder'
require_relative '../lib/paperclip/video_transcoder' require_relative '../lib/paperclip/video_transcoder'
@ -118,6 +119,7 @@ module Mastodon
config.active_job.queue_adapter = :sidekiq config.active_job.queue_adapter = :sidekiq
config.middleware.insert_before Rack::Runtime, HandleBadEncodingMiddleware
config.middleware.use Rack::Attack config.middleware.use Rack::Attack
config.middleware.use Rack::Deflater config.middleware.use Rack::Deflater

View File

@ -8,20 +8,15 @@ Doorkeeper.configure do
end end
resource_owner_from_credentials do |_routes| resource_owner_from_credentials do |_routes|
if Devise.ldap_authentication user = User.authenticate_with_ldap(email: request.params[:username], password: request.params[:password]) if Devise.ldap_authentication
user = User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] }) user ||= User.authenticate_with_pam(email: request.params[:username], password: request.params[:password]) if Devise.pam_authentication
end
if Devise.pam_authentication
user ||= User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] })
end
if user.nil? if user.nil?
user = User.find_by(email: request.params[:username]) 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 end
user if !user&.otp_required_for_login? user unless user&.otp_required_for_login?
end end
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Paperclip::DataUriAdapter.register
Paperclip.interpolates :filename do |attachment, style| Paperclip.interpolates :filename do |attachment, style|
if style == :original if style == :original
attachment.original_filename attachment.original_filename

View File

@ -46,10 +46,7 @@ class Rack::Attack
PROTECTED_PATHS_REGEX = Regexp.union(PROTECTED_PATHS.map { |path| /\A#{Regexp.escape(path)}/ }) 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| 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' req.remote_ip == '127.0.0.1' || req.remote_ip == '::1'
end end

View File

@ -594,6 +594,10 @@ en:
animations_and_accessibility: Animations and accessibility animations_and_accessibility: Animations and accessibility
confirmation_dialogs: Confirmation dialogs confirmation_dialogs: Confirmation dialogs
discovery: Discovery 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 sensitive_content: Sensitive content
toot_layout: Toot layout toot_layout: Toot layout
application_mailer: application_mailer:

View File

@ -1,5 +1,6 @@
class FixNullBooleans < ActiveRecord::Migration[5.1] class FixNullBooleans < ActiveRecord::Migration[5.1]
def change def change
safety_assured do
change_column_default :domain_blocks, :reject_media, false change_column_default :domain_blocks, :reject_media, false
change_column_null :domain_blocks, :reject_media, false, false change_column_null :domain_blocks, :reject_media, false, false
@ -15,3 +16,4 @@ class FixNullBooleans < ActiveRecord::Migration[5.1]
change_column_null :users, :otp_required_for_login, false, false change_column_null :users, :otp_required_for_login, false, false
end end
end end
end

View File

@ -1,6 +1,8 @@
class ChangeAccountsNonnullableInAccountModerationNotes < ActiveRecord::Migration[5.1] class ChangeAccountsNonnullableInAccountModerationNotes < ActiveRecord::Migration[5.1]
def change def change
safety_assured do
change_column_null :account_moderation_notes, :account_id, false change_column_null :account_moderation_notes, :account_id, false
change_column_null :account_moderation_notes, :target_account_id, false change_column_null :account_moderation_notes, :target_account_id, false
end end
end end
end

View File

@ -1,5 +1,7 @@
class ChangeAccountIdNonnullableInLists < ActiveRecord::Migration[5.1] class ChangeAccountIdNonnullableInLists < ActiveRecord::Migration[5.1]
def change def change
safety_assured do
change_column_null :lists, :account_id, false change_column_null :lists, :account_id, false
end end
end end
end

View File

@ -1,8 +1,10 @@
class ChangeColumnsInNotificationsNonnullable < ActiveRecord::Migration[5.1] class ChangeColumnsInNotificationsNonnullable < ActiveRecord::Migration[5.1]
def change def change
safety_assured do
change_column_null :notifications, :activity_id, false change_column_null :notifications, :activity_id, false
change_column_null :notifications, :activity_type, false change_column_null :notifications, :activity_type, false
change_column_null :notifications, :account_id, false change_column_null :notifications, :account_id, false
change_column_null :notifications, :from_account_id, false change_column_null :notifications, :from_account_id, false
end end
end end
end

View File

@ -60,7 +60,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.7.5", "@babel/core": "^7.7.7",
"@babel/plugin-proposal-class-properties": "^7.7.4", "@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.7.4", "@babel/plugin-proposal-decorators": "^7.7.4",
"@babel/plugin-transform-react-inline-elements": "^7.7.4", "@babel/plugin-transform-react-inline-elements": "^7.7.4",
@ -69,7 +69,7 @@
"@babel/preset-react": "^7.7.4", "@babel/preset-react": "^7.7.4",
"@babel/runtime": "^7.7.7", "@babel/runtime": "^7.7.7",
"@gamestdio/websocket": "^0.3.2", "@gamestdio/websocket": "^0.3.2",
"@clusterws/cws": "^0.16.0", "@clusterws/cws": "^0.16.1",
"array-includes": "^3.1.1", "array-includes": "^3.1.1",
"atrament": "^0.2.3", "atrament": "^0.2.3",
"arrow-key-navigation": "^1.1.0", "arrow-key-navigation": "^1.1.0",
@ -107,7 +107,7 @@
"intl": "^1.2.5", "intl": "^1.2.5",
"intl-messageformat": "^2.2.0", "intl-messageformat": "^2.2.0",
"intl-relativeformat": "^6.4.3", "intl-relativeformat": "^6.4.3",
"is-nan": "^1.2.1", "is-nan": "^1.3.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"lodash": "^4.17.14", "lodash": "^4.17.14",
"mark-loader": "^0.1.6", "mark-loader": "^0.1.6",
@ -146,7 +146,7 @@
"react-textarea-autosize": "^7.1.2", "react-textarea-autosize": "^7.1.2",
"react-toggle": "^4.1.1", "react-toggle": "^4.1.1",
"redis": "^2.7.1", "redis": "^2.7.1",
"redux": "^4.0.4", "redux": "^4.0.5",
"redux-immutable": "^4.0.0", "redux-immutable": "^4.0.0",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"rellax": "^1.10.0", "rellax": "^1.10.0",
@ -157,13 +157,13 @@
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"stringz": "^2.0.0", "stringz": "^2.0.0",
"substring-trie": "^1.0.2", "substring-trie": "^1.0.2",
"terser-webpack-plugin": "^2.2.2", "terser-webpack-plugin": "^2.3.1",
"tesseract.js": "^2.0.0-alpha.16", "tesseract.js": "^2.0.0-alpha.16",
"throng": "^4.0.0", "throng": "^4.0.0",
"tiny-queue": "^0.2.1", "tiny-queue": "^0.2.1",
"uuid": "^3.3.3", "uuid": "^3.3.3",
"wavesurfer.js": "^3.2.0", "wavesurfer.js": "^3.2.0",
"webpack": "^4.41.2", "webpack": "^4.41.5",
"webpack-assets-manifest": "^3.1.1", "webpack-assets-manifest": "^3.1.1",
"webpack-bundle-analyzer": "^3.6.0", "webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.10", "webpack-cli": "^3.3.10",
@ -175,7 +175,7 @@
"babel-jest": "^24.9.0", "babel-jest": "^24.9.0",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.1", "enzyme-adapter-react-16": "^1.15.1",
"eslint": "^6.7.2", "eslint": "^6.8.0",
"eslint-plugin-import": "~2.19.1", "eslint-plugin-import": "~2.19.1",
"eslint-plugin-jsx-a11y": "~6.2.3", "eslint-plugin-jsx-a11y": "~6.2.3",
"eslint-plugin-promise": "~4.2.1", "eslint-plugin-promise": "~4.2.1",
@ -183,7 +183,7 @@
"jest": "^24.9.0", "jest": "^24.9.0",
"raf": "^3.4.1", "raf": "^3.4.1",
"react-intl-translations-manager": "^5.0.3", "react-intl-translations-manager": "^5.0.3",
"react-test-renderer": "^16.11.0", "react-test-renderer": "^16.12.0",
"sass-lint": "^1.13.1", "sass-lint": "^1.13.1",
"webpack-dev-server": "^3.9.0", "webpack-dev-server": "^3.9.0",
"yargs": "^15.0.2" "yargs": "^15.0.2"

View File

@ -85,10 +85,7 @@ describe Api::ProofsController do
end end
it 'has the correct avatar url' do it 'has the correct avatar url' do
first_part = 'https://cb6e6126.ngrok.io/system/accounts/avatars/' expect(body_as_json[:avatar]).to match "https://cb6e6126.ngrok.io#{alice.avatar.url}"
last_part = 'original/avatar.gif'
expect(body_as_json[:avatar]).to match /#{Regexp.quote(first_part)}(?:\d{3,5}\/){3}#{Regexp.quote(last_part)}/
end end
end end
end end

View File

@ -5,17 +5,36 @@ describe Api::V1::Accounts::FollowerAccountsController do
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } 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 before do
Fabricate(:follow, target_account: user.account) alice.follow!(account)
bob.follow!(account)
allow(controller).to receive(:doorkeeper_token) { token } allow(controller).to receive(:doorkeeper_token) { token }
end end
describe 'GET #index' do describe 'GET #index' do
it 'returns http success' 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) expect(response).to have_http_status(200)
end 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
end end

View File

@ -5,17 +5,36 @@ describe Api::V1::Accounts::FollowingAccountsController do
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } 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 before do
Fabricate(:follow, account: user.account) account.follow!(alice)
account.follow!(bob)
allow(controller).to receive(:doorkeeper_token) { token } allow(controller).to receive(:doorkeeper_token) { token }
end end
describe 'GET #index' do describe 'GET #index' do
it 'returns http success' 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) expect(response).to have_http_status(200)
end 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
end end

View File

@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } 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(: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 context 'with an oauth token' do
before do before do
@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
let(:status) { Fabricate(:status, account: user.account) } let(:status) { Fabricate(:status, account: user.account) }
before do before do
Fabricate(:favourite, status: status) Favourite.create!(account: alice, status: status)
Favourite.create!(account: bob, status: status)
end end
it 'returns http success' do 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).to have_http_status(200)
expect(response.headers['Link'].links.size).to eq(2) expect(response.headers['Link'].links.size).to eq(2)
end 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
end end

View File

@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } 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(: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 context 'with an oauth token' do
before do before do
@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
let(:status) { Fabricate(:status, account: user.account) } let(:status) { Fabricate(:status, account: user.account) }
before do 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 end
it 'returns http success' do 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).to have_http_status(200)
expect(response.headers['Link'].links.size).to eq(2) expect(response.headers['Link'].links.size).to eq(2)
end 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
end end

View File

@ -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

View File

@ -22,6 +22,18 @@ describe FollowerAccountsController do
expect(assigned[0]).to eq follow1 expect(assigned[0]).to eq follow1
expect(assigned[1]).to eq follow0 expect(assigned[1]).to eq follow0
end 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 end
context 'when format is json' do context 'when format is json' do

View File

@ -22,6 +22,18 @@ describe FollowingAccountsController do
expect(assigned[0]).to eq follow1 expect(assigned[0]).to eq follow1
expect(assigned[1]).to eq follow0 expect(assigned[1]).to eq follow0
end 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 end
context 'when format is json' do context 'when format is json' do

View File

@ -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(:email) { "test@example.com" }
given(:password) { "password" } given(:password) { "password" }
given(:confirmed_at) { Time.zone.now } given(:confirmed_at) { Time.zone.now }
background do background do
Fabricate(:user, email: email, password: password, confirmed_at: confirmed_at) as_a_registered_user
visit new_user_session_path visit new_user_session_path
end end
subject { page } subject { page }
scenario "A valid email and password user is able to log in" do scenario 'A valid email and password user is able to log in' do
fill_in "user_email", with: email fill_in 'user_email', with: email
fill_in "user_password", with: password fill_in 'user_password', with: password
click_on I18n.t('auth.login') click_on I18n.t('auth.login')
is_expected.to have_css("div.app-holder") is_expected.to have_css('div.app-holder')
end end
scenario "A invalid email and password user is not able to log in" do scenario 'A invalid email and password user is not able to log in' do
fill_in "user_email", with: "invalid_email" fill_in 'user_email', with: 'invalid_email'
fill_in "user_password", with: "invalid_password" fill_in 'user_password', with: 'invalid_password'
click_on I18n.t('auth.login') 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 end
context do context do
given(:confirmed_at) { nil } given(:confirmed_at) { nil }
scenario "A unconfirmed user is able to log in" do scenario 'A unconfirmed user is able to log in' do
fill_in "user_email", with: email fill_in 'user_email', with: email
fill_in "user_password", with: password fill_in 'user_password', with: password
click_on I18n.t('auth.login') click_on I18n.t('auth.login')
is_expected.to have_css("div.admin-wrapper") is_expected.to have_css('div.admin-wrapper')
end end
end end
def failure_message(message) def failure_message(message)
keys = User.authentication_keys.map { |key| User.human_attribute_name(key) } 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
end end

View File

@ -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

View File

@ -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

View File

@ -823,4 +823,5 @@ RSpec.describe Account, type: :model do
end end
include_examples 'AccountAvatar', :account include_examples 'AccountAvatar', :account
include_examples 'AccountHeader', :account
end end

View File

@ -133,6 +133,24 @@ RSpec.describe MediaAttachment, type: :model do
expect(media.file.meta["small"]["height"]).to eq 327 expect(media.file.meta["small"]["height"]).to eq 327
expect(media.file.meta["small"]["aspect"]).to eq 490.0 / 327 expect(media.file.meta["small"]["aspect"]).to eq 490.0 / 327
end 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 end
describe 'descriptions for remote attachments' do describe 'descriptions for remote attachments' do

View File

@ -5,11 +5,11 @@ RSpec.describe ProcessMentionsService, type: :service do
let(:visibility) { :public } let(:visibility) { :public }
let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) } let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) }
subject { ProcessMentionsService.new }
context 'OStatus with public toot' do 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') } let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
subject { ProcessMentionsService.new }
before do before do
stub_request(:post, remote_user.salmon_url) stub_request(:post, remote_user.salmon_url)
subject.call(status) subject.call(status)
@ -24,8 +24,6 @@ RSpec.describe ProcessMentionsService, type: :service do
let(:visibility) { :private } let(:visibility) { :private }
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') } let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
subject { ProcessMentionsService.new }
before do before do
stub_request(:post, remote_user.salmon_url) stub_request(:post, remote_user.salmon_url)
subject.call(status) subject.call(status)
@ -41,10 +39,9 @@ RSpec.describe ProcessMentionsService, type: :service do
end end
context 'ActivityPub' do context 'ActivityPub' do
context do
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
subject { ProcessMentionsService.new }
before do before do
stub_request(:post, remote_user.inbox_url) stub_request(:post, remote_user.inbox_url)
subject.call(status) subject.call(status)
@ -59,11 +56,28 @@ RSpec.describe ProcessMentionsService, type: :service do
end end
end 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") }
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 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) } 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 before do
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404) 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) stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500)

View File

@ -16,4 +16,24 @@ shared_examples 'AccountAvatar' do |fabricator|
end end
end 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 end

View File

@ -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

View File

@ -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

View File

@ -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

146
yarn.lock
View File

@ -9,15 +9,15 @@
dependencies: dependencies:
"@babel/highlight" "^7.0.0" "@babel/highlight" "^7.0.0"
"@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.5": "@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.7":
version "7.7.5" version "7.7.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.7.tgz#ee155d2e12300bcc0cff6a8ad46f2af5063803e9"
integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw== integrity sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ==
dependencies: dependencies:
"@babel/code-frame" "^7.5.5" "@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.7.4" "@babel/generator" "^7.7.7"
"@babel/helpers" "^7.7.4" "@babel/helpers" "^7.7.4"
"@babel/parser" "^7.7.5" "@babel/parser" "^7.7.7"
"@babel/template" "^7.7.4" "@babel/template" "^7.7.4"
"@babel/traverse" "^7.7.4" "@babel/traverse" "^7.7.4"
"@babel/types" "^7.7.4" "@babel/types" "^7.7.4"
@ -29,10 +29,10 @@
semver "^5.4.1" semver "^5.4.1"
source-map "^0.5.0" source-map "^0.5.0"
"@babel/generator@^7.0.0", "@babel/generator@^7.7.4": "@babel/generator@^7.0.0", "@babel/generator@^7.7.4", "@babel/generator@^7.7.7":
version "7.7.4" version "7.7.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.4.tgz#db651e2840ca9aa66f327dcec1dc5f5fa9611369" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.7.tgz#859ac733c44c74148e1a72980a64ec84b85f4f45"
integrity sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg== integrity sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==
dependencies: dependencies:
"@babel/types" "^7.7.4" "@babel/types" "^7.7.4"
jsesc "^2.5.1" jsesc "^2.5.1"
@ -252,10 +252,10 @@
esutils "^2.0.2" esutils "^2.0.2"
js-tokens "^4.0.0" 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": "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.7":
version "7.7.5" version "7.7.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.7.tgz#1b886595419cf92d811316d5b715a53ff38b4937"
integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig== integrity sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==
"@babel/plugin-proposal-async-generator-functions@^7.7.4": "@babel/plugin-proposal-async-generator-functions@^7.7.4":
version "7.7.4" version "7.7.4"
@ -795,10 +795,10 @@
lodash "^4.17.13" lodash "^4.17.13"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@clusterws/cws@^0.16.0": "@clusterws/cws@^0.16.1":
version "0.16.0" version "0.16.1"
resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.0.tgz#f6116cbf3a8b7ad0657916616ce5f8248746b797" resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.1.tgz#443c47909601e87b881ffb2c4c83cbf27a228487"
integrity sha512-YeGpAPIdkBsOnAkmFKVMWEjCKDH900U2if0B+nc1imfv+64AIb2JX2xiTA6BLDLppEgWV5c6bpWESjbHCNblHw== integrity sha512-OamMsXwVppfmwX14Ed1msJJN0KNi+VBQh5AkAqUvIGTcJyHcjpsKjU15wS8QumvEewxx9gvGp7aSDop0/R5Gqg==
"@cnakazawa/watch@^1.0.3": "@cnakazawa/watch@^1.0.3":
version "1.0.3" version "1.0.3"
@ -3247,7 +3247,7 @@ default-gateway@^4.2.0:
execa "^1.0.0" execa "^1.0.0"
ip-regex "^2.1.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" version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
@ -3942,10 +3942,10 @@ eslint@^2.7.0:
text-table "~0.2.0" text-table "~0.2.0"
user-home "^2.0.0" user-home "^2.0.0"
eslint@^6.7.2: eslint@^6.8.0:
version "6.7.2" version "6.8.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.7.2.tgz#c17707ca4ad7b2d8af986a33feba71e18a9fecd1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
integrity sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng== integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
ajv "^6.10.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" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
esutils@^2.0.0: esutils@^2.0.0, esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
esutils@^2.0.2:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 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" make-dir "^2.0.0"
pkg-dir "^3.0.0" pkg-dir "^3.0.0"
find-cache-dir@^3.0.0, find-cache-dir@^3.1.0: find-cache-dir@^3.0.0, find-cache-dir@^3.2.0:
version "3.1.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.1.0.tgz#9935894999debef4cf9f677fdf646d002c4cdecb" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874"
integrity sha512-zw+EFiNBNPgI2NTrKkDd1xd7q0cs6wr/iWnr/oUkI0yF9K9GqQ+riIt4aiyFaaqpaWbxPrJXHI+QvmNUQbX+0Q== integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==
dependencies: dependencies:
commondir "^1.0.1" commondir "^1.0.1"
make-dir "^3.0.0" make-dir "^3.0.0"
@ -5620,12 +5615,12 @@ is-my-json-valid@^2.10.0:
jsonpointer "^4.0.0" jsonpointer "^4.0.0"
xtend "^4.0.0" xtend "^4.0.0"
is-nan@^1.2.1: is-nan@^1.3.0:
version "1.2.1" version "1.3.0"
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03"
integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= integrity sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ==
dependencies: dependencies:
define-properties "^1.1.1" define-properties "^1.1.3"
is-number-object@^1.0.4: is-number-object@^1.0.4:
version "1.0.4" version "1.0.4"
@ -8785,15 +8780,15 @@ react-swipeable-views@^0.13.3:
react-swipeable-views-utils "^0.13.3" react-swipeable-views-utils "^0.13.3"
warning "^4.0.1" warning "^4.0.1"
react-test-renderer@^16.0.0-0, react-test-renderer@^16.11.0: react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0:
version "16.11.0" version "16.12.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.12.0.tgz#11417ffda579306d4e841a794d32140f3da1b43f"
integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag== integrity sha512-Vj/teSqt2oayaWxkbhQ6gKis+t5JrknXfPVo+aIJ8QwYAqMPH77uptOdrlphyxl8eQI/rtkOYg86i/UWkpFu0w==
dependencies: dependencies:
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types "^15.6.2" prop-types "^15.6.2"
react-is "^16.8.6" react-is "^16.8.6"
scheduler "^0.17.0" scheduler "^0.18.0"
react-textarea-autosize@^7.1.2: react-textarea-autosize@^7.1.2:
version "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" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@^4.0.4: redux@^4.0.5:
version "4.0.4" version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
dependencies: dependencies:
loose-envify "^1.4.0" loose-envify "^1.4.0"
symbol-observable "^1.2.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" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== 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: scheduler@^0.18.0:
version "0.18.0" version "0.18.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" 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" range-parser "~1.2.1"
statuses "~1.5.0" statuses "~1.5.0"
serialize-javascript@^1.7.0: serialize-javascript@^2.1.2:
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:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
@ -10164,39 +10146,39 @@ tcomb@^2.5.0:
resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0"
integrity sha1-ENYpWAQWaaXVNWe5pO6M3iKxwrA= integrity sha1-ENYpWAQWaaXVNWe5pO6M3iKxwrA=
terser-webpack-plugin@^1.4.1: terser-webpack-plugin@^1.4.3:
version "1.4.1" version "1.4.3"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c"
integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==
dependencies: dependencies:
cacache "^12.0.2" cacache "^12.0.2"
find-cache-dir "^2.1.0" find-cache-dir "^2.1.0"
is-wsl "^1.1.0" is-wsl "^1.1.0"
schema-utils "^1.0.0" schema-utils "^1.0.0"
serialize-javascript "^1.7.0" serialize-javascript "^2.1.2"
source-map "^0.6.1" source-map "^0.6.1"
terser "^4.1.2" terser "^4.1.2"
webpack-sources "^1.4.0" webpack-sources "^1.4.0"
worker-farm "^1.7.0" worker-farm "^1.7.0"
terser-webpack-plugin@^2.2.2: terser-webpack-plugin@^2.3.1:
version "2.2.2" version "2.3.1"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.2.2.tgz#2a6e00237125564a455ad69b22e08ee59420473a" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.1.tgz#6a63c27debc15b25ffd2588562ee2eeabdcab923"
integrity sha512-/CHMNswPMAwuD2kd++qys8UmBRmsshPSzHw4BlDwurPtK9YjeK93OV89YWkJulHk972cs07K/7Z92V6PNjWF8A== integrity sha512-dNxivOXmDgZqrGxOttBH6B4xaxT4zNC+Xd+2K8jwGDMK5q2CZI+KZMA1AAnSRT+BTRvuzKsDx+fpxzPAmAMVcA==
dependencies: dependencies:
cacache "^13.0.1" cacache "^13.0.1"
find-cache-dir "^3.1.0" find-cache-dir "^3.2.0"
jest-worker "^24.9.0" jest-worker "^24.9.0"
schema-utils "^2.6.1" schema-utils "^2.6.1"
serialize-javascript "^2.1.1" serialize-javascript "^2.1.2"
source-map "^0.6.1" source-map "^0.6.1"
terser "^4.4.2" terser "^4.4.3"
webpack-sources "^1.4.3" webpack-sources "^1.4.3"
terser@^4.1.2, terser@^4.4.2: terser@^4.1.2, terser@^4.4.3:
version "4.4.2" version "4.4.3"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.2.tgz#448fffad0245f4c8a277ce89788b458bfd7706e8" resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.3.tgz#401abc52b88869cf904412503b1eb7da093ae2f0"
integrity sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ== integrity sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA==
dependencies: dependencies:
commander "^2.20.0" commander "^2.20.0"
source-map "~0.6.1" 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-list-map "^2.0.0"
source-map "~0.6.1" source-map "~0.6.1"
webpack@^4.41.2: webpack@^4.41.5:
version "4.41.2" version "4.41.5"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.5.tgz#3210f1886bce5310e62bb97204d18c263341b77c"
integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A== integrity sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw==
dependencies: dependencies:
"@webassemblyjs/ast" "1.8.5" "@webassemblyjs/ast" "1.8.5"
"@webassemblyjs/helper-module-context" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5"
@ -10883,7 +10865,7 @@ webpack@^4.41.2:
node-libs-browser "^2.2.1" node-libs-browser "^2.2.1"
schema-utils "^1.0.0" schema-utils "^1.0.0"
tapable "^1.1.3" tapable "^1.1.3"
terser-webpack-plugin "^1.4.1" terser-webpack-plugin "^1.4.3"
watchpack "^1.6.0" watchpack "^1.6.0"
webpack-sources "^1.4.1" webpack-sources "^1.4.1"