diff options
90 files changed, 891 insertions, 660 deletions
diff --git a/Gemfile b/Gemfile index f654ae737..deef23af5 100644 --- a/Gemfile +++ b/Gemfile @@ -62,7 +62,7 @@ gem 'mime-types', '~> 3.2', require: 'mime/types/columnar' gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532' gem 'nokogiri', '~> 1.10' gem 'nsa', '~> 0.2' -gem 'oj', '~> 3.7' +gem 'oj', '~> 3.8' gem 'ostatus2', '~> 2.0' gem 'ox', '~> 2.11' gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' @@ -112,7 +112,7 @@ group :production, :test do end group :test do - gem 'capybara', '~> 3.25' + gem 'capybara', '~> 3.26' gem 'climate_control', '~> 0.2' gem 'faker', '~> 1.9' gem 'microformats', '~> 4.1' @@ -132,7 +132,7 @@ group :development do gem 'letter_opener', '~> 1.7' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' - gem 'rubocop', '~> 0.72', require: false + gem 'rubocop', '~> 0.73', require: false gem 'rubocop-rails', '~> 2.2', require: false gem 'brakeman', '~> 4.5', require: false gem 'bundler-audit', '~> 0.6', require: false diff --git a/Gemfile.lock b/Gemfile.lock index fac6fb0cc..9c2aca4be 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -111,7 +111,7 @@ GEM bootsnap (1.4.4) msgpack (~> 1.0) brakeman (4.5.1) - browser (2.5.3) + browser (2.6.1) builder (3.2.3) bullet (6.0.1) activesupport (>= 3.0.0) @@ -136,7 +136,7 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.25.0) + capybara (3.26.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -163,7 +163,7 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.4) - css_parser (1.6.0) + css_parser (1.7.0) addressable debug_inspector (0.0.3) derailed_benchmarks (1.3.6) @@ -354,7 +354,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) mimemagic (0.3.3) - mini_mime (1.0.1) + mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.11.3) msgpack (1.2.10) @@ -375,7 +375,7 @@ GEM concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) - oj (3.7.12) + oj (3.8.0) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) @@ -417,8 +417,8 @@ GEM addressable css_parser (>= 1.6.0) htmlentities (>= 4.0.0) - premailer-rails (1.10.2) - actionmailer (>= 3, < 6) + premailer-rails (1.10.3) + actionmailer (>= 3) premailer (~> 1.7, >= 1.7.9) private_address_check (0.5.0) pry (0.12.2) @@ -505,7 +505,7 @@ GEM redis-store (>= 1.2, < 2) redis-store (1.5.0) redis (>= 2.2, < 5) - regexp_parser (1.5.1) + regexp_parser (1.6.0) request_store (1.4.1) rack (>= 1.4) responders (2.4.1) @@ -535,7 +535,7 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.8.0) - rubocop (0.72.0) + rubocop (0.73.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.6) @@ -671,7 +671,7 @@ DEPENDENCIES capistrano-rails (~> 1.4) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 3.25) + capybara (~> 3.26) charlock_holmes (~> 0.7.6) chewy (~> 5.0) cld3 (~> 3.2.4) @@ -719,7 +719,7 @@ DEPENDENCIES nilsimsa! nokogiri (~> 1.10) nsa (~> 0.2) - oj (~> 3.7) + oj (~> 3.8) omniauth (~> 1.9) omniauth-cas (~> 1.1) omniauth-saml (~> 1.10) @@ -752,7 +752,7 @@ DEPENDENCIES rqrcode (~> 0.10) rspec-rails (~> 3.8) rspec-sidekiq (~> 3.0) - rubocop (~> 0.72) + rubocop (~> 0.73) rubocop-rails (~> 2.2) sanitize (~> 5.0) sidekiq (~> 5.2) diff --git a/Procfile b/Procfile index b18e4b6be..d48b0373b 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,14 @@ -web: bundle exec puma -C config/puma.rb +web: if [ "$RUN_STREAMING" != "true" ]; then BIND=0.0.0.0 bundle exec puma -C config/puma.rb; else BIND=0.0.0.0 node ./streaming; fi worker: bundle exec sidekiq + +# For the streaming API, you need a separate app that shares Postgres and Redis: +# +# heroku create +# heroku buildpacks:add heroku/nodejs +# heroku config:set RUN_STREAMING=true +# heroku addons:attach <main-app>::DATABASE +# heroku addons:attach <main-app>::REDIS +# +# and let the main app use the separate app: +# +# heroku config:set STREAMING_API_BASE_URL=wss://<streaming-app>.herokuapp.com -a <main-app> diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index a6e33a5d9..179f013b5 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -8,7 +8,7 @@ class AboutController < ApplicationController before_action :set_instance_presenter before_action :set_expires_in - skip_before_action :check_user_permissions, only: [:more, :terms] + skip_before_action :require_functional!, only: [:more, :terms] def show; end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index ff684e31e..1aed1af8d 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -42,7 +42,7 @@ class AccountsController < ApplicationController format.json do expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?) - render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to + render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to end end end diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index fa925b204..989fee385 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -11,7 +11,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController def show expires_in 3.minutes, public: public_fetch_mode? - render json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true + render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true end private diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index eca558f42..6f33a1ea9 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController include RateLimitHeaders skip_before_action :store_current_location - skip_before_action :check_user_permissions + skip_before_action :require_functional! before_action :set_cache_headers diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index b6877fb3c..252f667dd 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -6,8 +6,7 @@ class Api::V1::CustomEmojisController < Api::BaseController skip_before_action :set_cache_headers def index - render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do - ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false).includes(:category), each_serializer: REST::CustomEmojiSerializer) - end + expires_in 3.minutes, public: true + render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.local.where(disabled: false).includes(:category) } end end diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb index 09edfe365..d0080c5c2 100644 --- a/app/controllers/api/v1/instances/activity_controller.rb +++ b/app/controllers/api/v1/instances/activity_controller.rb @@ -7,7 +7,8 @@ class Api::V1::Instances::ActivityController < Api::BaseController respond_to :json def show - render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity } + expires_in 1.day, public: true + render_with_cache json: :activity, expires_in: 1.day end private diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index a8891d126..450e6502f 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -7,7 +7,8 @@ class Api::V1::Instances::PeersController < Api::BaseController respond_to :json def index - render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains } + expires_in 1.day, public: true + render_with_cache(expires_in: 1.day) { Account.remote.domains } end private diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index 8c83a1801..b68c78615 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -5,8 +5,7 @@ class Api::V1::InstancesController < Api::BaseController skip_before_action :set_cache_headers def show - render_cached_json('api:v1:instances', expires_in: 5.minutes) do - ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer) - end + expires_in 3.minutes, public: true + render_with_cache json: {}, serializer: REST::InstanceSerializer end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 95e0d624f..4a6b96982 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base include Localized include UserTrackingConcern include SessionTrackingConcern + include CacheConcern helper_method :current_account helper_method :current_session @@ -25,7 +26,7 @@ class ApplicationController < ActionController::Base rescue_from Mastodon::NotPermittedError, with: :forbidden before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? - before_action :check_user_permissions, if: :user_signed_in? + before_action :require_functional!, if: :user_signed_in? def raise_not_found raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}" @@ -57,8 +58,8 @@ class ApplicationController < ActionController::Base forbidden unless current_user&.staff? end - def check_user_permissions - forbidden if current_user.disabled? || current_user.account.suspended? + def require_functional! + redirect_to edit_user_registration_path unless current_user.functional? end def after_sign_out_path_for(_resource_or_scope) @@ -190,52 +191,14 @@ class ApplicationController < ActionController::Base current_user.setting_skin end - def cache_collection(raw, klass) - return raw unless klass.respond_to?(:with_includes) - - raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation) - cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id) - uncached_ids = raw.map(&:id) - cached_keys_with_value.keys - - klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!) - - unless uncached_ids.empty? - uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item } - - uncached.each_value do |item| - Rails.cache.write(item, item) - end - end - - raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact - end - def respond_with_error(code) respond_to do |format| format.any { head code } format.html do - set_locale use_pack 'error' render "errors/#{code}", layout: 'error', status: code end end end - - def render_cached_json(cache_key, **options) - options[:expires_in] ||= 3.minutes - cache_public = options.key?(:public) ? options.delete(:public) : true - content_type = options.delete(:content_type) || 'application/json' - - data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do - yield.to_json - end - - expires_in options[:expires_in], public: cache_public - render json: data, content_type: content_type - end - - def set_cache_headers - response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature' - end end diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index eade82e36..1d6e4ec19 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -4,20 +4,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController layout 'auth' before_action :set_body_classes - before_action :set_user, only: [:finish_signup] before_action :set_pack - def finish_signup - return unless request.patch? && params[:user] - - if @user.update(user_params) - @user.skip_reconfirmation! - bypass_sign_in(@user) - redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions') - else - @show_errors = true - end - end + skip_before_action :require_functional! private @@ -25,18 +14,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController use_pack 'auth' end - def set_user - @user = current_user - end - def set_body_classes @body_classes = 'lighter' end - def user_params - params.require(:user).permit(:email) - end - def after_confirmation_path_for(_resource_name, user) if user.created_by_application && truthy_param?(:redirect_to_app) user.created_by_application.redirect_uri diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb index bbf63bed3..682c77016 100644 --- a/app/controllers/auth/omniauth_callbacks_controller.rb +++ b/app/controllers/auth/omniauth_callbacks_controller.rb @@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController if resource.email_verified? root_path else - finish_signup_path + auth_setup_path(missing_email: '1') end end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index c56728464..068375843 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -10,6 +10,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :set_sessions, only: [:edit, :update] before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update] + before_action :require_not_suspended!, only: [:update] + + skip_before_action :require_functional!, only: [:edit, :update] def new super(&:build_invite_request) @@ -44,7 +47,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def after_sign_up_path_for(_resource) - new_user_session_path + auth_setup_path end def after_sign_in_path_for(_resource) @@ -107,4 +110,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController def set_sessions @sessions = current_user.session_activations end + + def require_not_suspended! + forbidden if current_account.suspended? + end end diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 332f4d7a7..7ecbaf193 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -6,9 +6,11 @@ class Auth::SessionsController < Devise::SessionsController layout 'auth' skip_before_action :require_no_authentication, only: [:create] - skip_before_action :check_user_permissions, only: [:destroy] + skip_before_action :require_functional! + prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] prepend_before_action :set_pack + before_action :set_instance_presenter, only: [:new] before_action :set_body_classes diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb new file mode 100644 index 000000000..46c5f2958 --- /dev/null +++ b/app/controllers/auth/setup_controller.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class Auth::SetupController < ApplicationController + layout 'auth' + + before_action :authenticate_user! + before_action :require_unconfirmed_or_pending! + before_action :set_body_classes + before_action :set_user + + skip_before_action :require_functional! + + def show + flash.now[:notice] = begin + if @user.pending? + I18n.t('devise.registrations.signed_up_but_pending') + else + I18n.t('devise.registrations.signed_up_but_unconfirmed') + end + end + end + + def update + # This allows updating the e-mail without entering a password as is required + # on the account settings page; however, we only allow this for accounts + # that were not confirmed yet + + if @user.update(user_params) + redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions') + else + render :show + end + end + + helper_method :missing_email? + + private + + def require_unconfirmed_or_pending! + redirect_to root_path if current_user.confirmed? && current_user.approved? + end + + def set_user + @user = current_user + end + + def set_body_classes + @body_classes = 'lighter' + end + + def user_params + params.require(:user).permit(:email) + end + + def missing_email? + truthy_param?(:missing_email) + end +end diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb new file mode 100644 index 000000000..c7d25ae00 --- /dev/null +++ b/app/controllers/concerns/cache_concern.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module CacheConcern + extend ActiveSupport::Concern + + def render_with_cache(**options) + raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given? + + key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':') + expires_in = options.delete(:expires_in) || 3.minutes + body = Rails.cache.read(key, raw: true) + + if body + render(options.except(:json, :serializer, :each_serializer, :adapter, :fields).merge(json: body)) + else + if block_given? + options[:json] = yield + elsif options[:json].is_a?(Symbol) + options[:json] = send(options[:json]) + end + + render(options) + Rails.cache.write(key, response.body, expires_in: expires_in, raw: true) + end + end + + def set_cache_headers + response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature' + end + + def cache_collection(raw, klass) + return raw unless klass.respond_to?(:with_includes) + + raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation) + cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id) + uncached_ids = raw.map(&:id) - cached_keys_with_value.keys + + klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!) + + unless uncached_ids.empty? + uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item } + + uncached.each_value do |item| + Rails.cache.write(item, item) + end + end + + raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact + end +end diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb index 145549bcd..b43859d9d 100644 --- a/app/controllers/concerns/localized.rb +++ b/app/controllers/concerns/localized.rb @@ -4,16 +4,19 @@ module Localized extend ActiveSupport::Concern included do - before_action :set_locale + around_action :set_locale end private def set_locale - I18n.locale = default_locale - I18n.locale = current_user.locale if user_signed_in? - rescue I18n::InvalidLocale - I18n.locale = default_locale + locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in? + locale ||= session[:locale] ||= default_locale + locale = default_locale unless I18n.available_locales.include?(locale.to_sym) + + I18n.with_locale(locale) do + yield + end end def default_locale diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb index fe4c19cad..41f1e1c5c 100644 --- a/app/controllers/emojis_controller.rb +++ b/app/controllers/emojis_controller.rb @@ -8,7 +8,7 @@ class EmojisController < ApplicationController respond_to do |format| format.json do expires_in 3.minutes, public: true - render json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter + render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter end end end diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 4e45445df..c5ccece13 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -8,6 +8,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio before_action :set_pack before_action :set_body_classes + skip_before_action :require_functional! + include Localized def destroy diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb index d3ac268d8..ed3f82a8e 100644 --- a/app/controllers/settings/applications_controller.rb +++ b/app/controllers/settings/applications_controller.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class Settings::ApplicationsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! before_action :set_application, only: [:show, :update, :destroy, :regenerate] before_action :prepare_scopes, only: [:create, :update] diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb index 34ef16568..8c394a6d3 100644 --- a/app/controllers/settings/base_controller.rb +++ b/app/controllers/settings/base_controller.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true class Settings::BaseController < ApplicationController - layout 'admin' - - before_action :authenticate_user! before_action :set_pack before_action :set_body_classes + private + def set_pack use_pack 'settings' end diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb index 4c1121471..97fe4d328 100644 --- a/app/controllers/settings/deletes_controller.rb +++ b/app/controllers/settings/deletes_controller.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true class Settings::DeletesController < Settings::BaseController + layout 'admin' - prepend_before_action :check_enabled_deletion + before_action :check_enabled_deletion + before_action :authenticate_user! + before_action :require_not_suspended! + + skip_before_action :require_functional! def show @confirmation = Form::DeleteConfirmation.new @@ -27,4 +32,8 @@ class Settings::DeletesController < Settings::BaseController def delete_params params.require(:form_delete_confirmation).permit(:password) end + + def require_not_suspended! + forbidden if current_account.suspended? + end end diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb index 7f76668d5..3012fbf77 100644 --- a/app/controllers/settings/exports_controller.rb +++ b/app/controllers/settings/exports_controller.rb @@ -3,6 +3,10 @@ class Settings::ExportsController < Settings::BaseController include Authorization + layout 'admin' + + before_action :authenticate_user! + def show @export = Export.new(current_account) @backups = current_user.backups diff --git a/app/controllers/settings/flavours_controller.rb b/app/controllers/settings/flavours_controller.rb index 634387715..62c52eee9 100644 --- a/app/controllers/settings/flavours_controller.rb +++ b/app/controllers/settings/flavours_controller.rb @@ -1,6 +1,12 @@ # frozen_string_literal: true class Settings::FlavoursController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + + skip_before_action :require_functional! + def index redirect_to action: 'show', flavour: current_flavour end diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index dbd136ebe..38f2e39c1 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class Settings::ImportsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! before_action :set_account def show diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb index 89b3f7246..59eb48779 100644 --- a/app/controllers/settings/migrations_controller.rb +++ b/app/controllers/settings/migrations_controller.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class Settings::MigrationsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + def show @migration = Form::Migration.new(account: current_account.moved_to_account) end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 372f253cb..ab6b5c0b0 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class Settings::PreferencesController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + def show; end def update diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 76d599f08..8b640cdca 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -3,6 +3,9 @@ class Settings::ProfilesController < Settings::BaseController include ObfuscateFilename + layout 'admin' + + before_action :authenticate_user! before_action :set_account obfuscate_filename [:account, :avatar] diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb index d74db6000..f8fb4036e 100644 --- a/app/controllers/settings/sessions_controller.rb +++ b/app/controllers/settings/sessions_controller.rb @@ -5,6 +5,8 @@ class Settings::SessionsController < ApplicationController before_action :authenticate_user! before_action :set_session, only: :destroy + skip_before_action :require_functional! + def destroy @session.destroy! flash[:notice] = I18n.t('sessions.revoke_success') diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb index 363b32e17..3145e092d 100644 --- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb +++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb @@ -3,8 +3,13 @@ module Settings module TwoFactorAuthentication class ConfirmationsController < BaseController + layout 'admin' + + before_action :authenticate_user! before_action :ensure_otp_secret + skip_before_action :require_functional! + def new prepare_two_factor_form end diff --git a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb index 0555d61db..09a759860 100644 --- a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb +++ b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb @@ -3,6 +3,12 @@ module Settings module TwoFactorAuthentication class RecoveryCodesController < BaseController + layout 'admin' + + before_action :authenticate_user! + + skip_before_action :require_functional! + def create @recovery_codes = current_user.generate_otp_backup_codes! current_user.save! diff --git a/app/controllers/settings/two_factor_authentications_controller.rb b/app/controllers/settings/two_factor_authentications_controller.rb index 8c7737e9d..6904076e4 100644 --- a/app/controllers/settings/two_factor_authentications_controller.rb +++ b/app/controllers/settings/two_factor_authentications_controller.rb @@ -2,8 +2,13 @@ module Settings class TwoFactorAuthenticationsController < BaseController + layout 'admin' + + before_action :authenticate_user! before_action :verify_otp_required, only: [:create] + skip_before_action :require_functional! + def show @confirmation = Form::TwoFactorConfirmation.new end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 0190a3c54..3d7e61e77 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -34,14 +34,14 @@ class StatusesController < ApplicationController format.json do expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? - render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter + render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter end end end def activity expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? - render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter + render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter end def embed diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index c19ca8265..52d85c059 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -22,7 +22,7 @@ export function normalizeAccount(account) { if (account.fields) { account.fields = account.fields.map(pair => ({ ...pair, - name_emojified: emojify(escapeTextContentForBrowser(pair.name)), + name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap), value_emojified: emojify(pair.value, emojiMap), value_plain: unescapeHTML(pair.value), })); diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js index 7f6ef5a5d..9d8c4a775 100644 --- a/app/javascript/flavours/glitch/components/display_name.js +++ b/app/javascript/flavours/glitch/components/display_name.js @@ -1,73 +1,111 @@ -// Package imports. -import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { autoPlayGif } from 'flavours/glitch/util/initial_state'; + +export default class DisplayName extends React.PureComponent { + + static propTypes = { + account: ImmutablePropTypes.map, + className: PropTypes.string, + inline: PropTypes.bool, + localDomain: PropTypes.string, + others: ImmutablePropTypes.list, + handleClick: PropTypes.func, + }; + + _updateEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } -// The component. -export default function DisplayName ({ - account, - className, - inline, - localDomain, - others, - onAccountClick, -}) { - const computedClass = classNames('display-name', { inline }, className); + componentDidMount () { + this._updateEmojis(); + } - if (!account) return null; + componentDidUpdate () { + this._updateEmojis(); + } - let displayName, suffix; + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } - let acct = account.get('acct'); + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } - if (acct.indexOf('@') === -1 && localDomain) { - acct = `${acct}@${localDomain}`; + setRef = (c) => { + this.node = c; } - if (others && others.size > 0) { - displayName = others.take(2).map(a => ( - <a - href={a.get('url')} - target='_blank' - onClick={(e) => onAccountClick(a.get('id'), e)} - title={`@${a.get('acct')}`} - > - <bdi key={a.get('id')}> - <strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /> - </bdi> - </a> - )).reduce((prev, cur) => [prev, ', ', cur]); - - if (others.size - 2 > 0) { - displayName.push(` +${others.size - 2}`); + render() { + const { account, className, inline, localDomain, others, onAccountClick } = this.props; + + const computedClass = classNames('display-name', { inline }, className); + + if (!account) return null; + + let displayName, suffix; + + let acct = account.get('acct'); + + if (acct.indexOf('@') === -1 && localDomain) { + acct = `${acct}@${localDomain}`; } - suffix = ( - <a href={account.get('url')} target='_blank' onClick={(e) => onAccountClick(account.get('id'), e)}> - <span className='display-name__account'>@{acct}</span> - </a> + if (others && others.size > 0) { + displayName = others.take(2).map(a => ( + <a + href={a.get('url')} + target='_blank' + onClick={(e) => onAccountClick(a.get('id'), e)} + title={`@${a.get('acct')}`} + > + <bdi key={a.get('id')}> + <strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /> + </bdi> + </a> + )).reduce((prev, cur) => [prev, ', ', cur]); + + if (others.size - 2 > 0) { + displayName.push(` +${others.size - 2}`); + } + + suffix = ( + <a href={account.get('url')} target='_blank' onClick={(e) => onAccountClick(account.get('id'), e)}> + <span className='display-name__account'>@{acct}</span> + </a> + ); + } else { + displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>; + suffix = <span className='display-name__account'>@{acct}</span>; + } + + return ( + <span className={computedClass} ref={this.setRef}> + {displayName} + {inline ? ' ' : null} + {suffix} + </span> ); - } else { - displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>; - suffix = <span className='display-name__account'>@{acct}</span>; } - return ( - <span className={computedClass}> - {displayName} - {inline ? ' ' : null} - {suffix} - </span> - ); } - -// Props. -DisplayName.propTypes = { - account: ImmutablePropTypes.map, - className: PropTypes.string, - inline: PropTypes.bool, - localDomain: PropTypes.string, - others: ImmutablePropTypes.list, - handleClick: PropTypes.func, -}; diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js index 07a0d1d5d..650b834de 100644 --- a/app/javascript/flavours/glitch/components/status_content.js +++ b/app/javascript/flavours/glitch/components/status_content.js @@ -5,6 +5,7 @@ import { isRtl } from 'flavours/glitch/util/rtl'; import { FormattedMessage } from 'react-intl'; import Permalink from './permalink'; import classnames from 'classnames'; +import { autoPlayGif } from 'flavours/glitch/util/initial_state'; export default class StatusContent extends React.PureComponent { @@ -57,12 +58,35 @@ export default class StatusContent extends React.PureComponent { } } + _updateStatusEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + componentDidMount () { this._updateStatusLinks(); + this._updateStatusEmojis(); } componentDidUpdate () { this._updateStatusLinks(); + this._updateStatusEmojis(); if (this.props.onUpdate) this.props.onUpdate(); } @@ -86,6 +110,14 @@ export default class StatusContent extends React.PureComponent { } } + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + handleMouseDown = (e) => { this.startXY = [e.clientX, e.clientY]; } @@ -194,7 +226,7 @@ export default class StatusContent extends React.PureComponent { } return ( - <div className={classNames} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}> + <div className={classNames} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} ref={this.setRef}> <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }} > @@ -209,7 +241,6 @@ export default class StatusContent extends React.PureComponent { <div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}> <div - ref={this.setRef} style={directionStyle} tabIndex={!hidden ? 0 : null} dangerouslySetInnerHTML={content} @@ -229,9 +260,9 @@ export default class StatusContent extends React.PureComponent { onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} tabIndex='0' + ref={this.setRef} > <div - ref={this.setRef} dangerouslySetInnerHTML={content} lang={status.get('language')} className='status__content__text' @@ -246,8 +277,9 @@ export default class StatusContent extends React.PureComponent { className='status__content' style={directionStyle} tabIndex='0' + ref={this.setRef} > - <div ref={this.setRef} className='status__content__text' dangerouslySetInnerHTML={content} lang={status.get('language')} tabIndex='0' /> + <div className='status__content__text' dangerouslySetInnerHTML={content} lang={status.get('language')} tabIndex='0' /> {media} </div> ); diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index 43c4f0d32..e9437c0a9 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -71,6 +71,47 @@ class Header extends ImmutablePureComponent { window.open('/settings/profile', '_blank'); } + _updateEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + + componentDidMount () { + this._updateEmojis(); + } + + componentDidUpdate () { + this._updateEmojis(); + } + + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + + setRef = (c) => { + this.node = c; + } + render () { const { account, intl, domain, identity_proofs } = this.props; @@ -193,7 +234,7 @@ class Header extends ImmutablePureComponent { const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); return ( - <div className={classNames('account__header', { inactive: !!account.get('moved') })}> + <div className={classNames('account__header', { inactive: !!account.get('moved') })} ref={this.setRef}> <div className='account__header__image'> <div className='account__header__info'> {info} diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index da0b4c8e0..9f88b0e04 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -4,6 +4,7 @@ import ready from 'flavours/glitch/util/ready'; function main() { const IntlMessageFormat = require('intl-messageformat').default; const { timeAgoString } = require('flavours/glitch/components/relative_timestamp'); + const { delegate } = require('rails-ujs'); const emojify = require('flavours/glitch/util/emoji').default; const { getLocale } = require('locales'); const { messages } = getLocale(); @@ -23,6 +24,12 @@ function main() { } }; + const getEmojiAnimationHandler = (swapTo) => { + return ({ target }) => { + target.src = target.getAttribute(swapTo); + }; + }; + ready(() => { const locale = document.documentElement.lang; @@ -94,6 +101,9 @@ function main() { document.head.appendChild(scrollbarWidthStyle); scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0); } + + delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); + delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); }); } diff --git a/app/javascript/flavours/glitch/util/emoji/index.js b/app/javascript/flavours/glitch/util/emoji/index.js index e6fcaf0dc..2f40f9b08 100644 --- a/app/javascript/flavours/glitch/util/emoji/index.js +++ b/app/javascript/flavours/glitch/util/emoji/index.js @@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => { // if you want additional emoji handler, add statements below which set replacement and return true. if (shortname in customEmojis) { const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; - replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`; + replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`; return true; } return false; diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index b250ee076..5e7e78e69 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -22,7 +22,7 @@ export function normalizeAccount(account) { if (account.fields) { account.fields = account.fields.map(pair => ({ ...pair, - name_emojified: emojify(escapeTextContentForBrowser(pair.name)), + name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap), value_emojified: emojify(pair.value, emojiMap), value_plain: unescapeHTML(pair.value), })); diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js index 6b9dd6f81..70ef82789 100644 --- a/app/javascript/mastodon/components/display_name.js +++ b/app/javascript/mastodon/components/display_name.js @@ -1,6 +1,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; +import { autoPlayGif } from 'mastodon/initial_state'; export default class DisplayName extends React.PureComponent { @@ -10,6 +11,47 @@ export default class DisplayName extends React.PureComponent { localDomain: PropTypes.string, }; + _updateEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + + componentDidMount () { + this._updateEmojis(); + } + + componentDidUpdate () { + this._updateEmojis(); + } + + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + + setRef = (c) => { + this.node = c; + } + render () { const { others, localDomain } = this.props; @@ -39,7 +81,7 @@ export default class DisplayName extends React.PureComponent { } return ( - <span className='display-name'> + <span className='display-name' ref={this.setRef}> {displayName} {suffix} </span> ); diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 06f5b4aad..8a05415af 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -7,6 +7,7 @@ import Permalink from './permalink'; import classnames from 'classnames'; import PollContainer from 'mastodon/containers/poll_container'; import Icon from 'mastodon/components/icon'; +import { autoPlayGif } from 'mastodon/initial_state'; const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top) @@ -71,12 +72,35 @@ export default class StatusContent extends React.PureComponent { } } + _updateStatusEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + componentDidMount () { this._updateStatusLinks(); + this._updateStatusEmojis(); } componentDidUpdate () { this._updateStatusLinks(); + this._updateStatusEmojis(); } onMentionClick = (mention, e) => { @@ -95,6 +119,14 @@ export default class StatusContent extends React.PureComponent { } } + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + handleMouseDown = (e) => { this.startXY = [e.clientX, e.clientY]; } diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 86324b846..fa58589a6 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -77,7 +77,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onReblog (status, e) { - if (e.shiftKey || !boostModal) { + if ((e && e.shiftKey) || !boostModal) { this.onModalReblog(status); } else { dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index e5b60e33e..cab67c607 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -79,6 +79,47 @@ class Header extends ImmutablePureComponent { return !location.pathname.match(/\/(followers|following)\/?$/); } + _updateEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + + componentDidMount () { + this._updateEmojis(); + } + + componentDidUpdate () { + this._updateEmojis(); + } + + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + + setRef = (c) => { + this.node = c; + } + render () { const { account, intl, domain, identity_proofs } = this.props; @@ -200,7 +241,7 @@ class Header extends ImmutablePureComponent { const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); return ( - <div className={classNames('account__header', { inactive: !!account.get('moved') })}> + <div className={classNames('account__header', { inactive: !!account.get('moved') })} ref={this.setRef}> <div className='account__header__image'> <div className='account__header__info'> {info} diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index 01b5a6664..359bb7ffd 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => { // if you want additional emoji handler, add statements below which set replacement and return true. if (shortname in customEmojis) { const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; - replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`; + replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`; return true; } return false; diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 69441d315..6aea119e3 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -27,6 +27,12 @@ function main() { } }; + const getEmojiAnimationHandler = (swapTo) => { + return ({ target }) => { + target.src = target.getAttribute(swapTo); + }; + }; + ready(() => { const locale = document.documentElement.lang; @@ -91,6 +97,9 @@ function main() { if (parallaxComponents.length > 0 ) { new Rellax('.parallax', { speed: -1 }); } + + delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); + delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); }); } diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 692d86852..9bb2561cd 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -204,29 +204,6 @@ $content-width: 840px; border: 0; } } - - .muted-hint { - color: $darker-text-color; - - a { - color: $highlight-text-color; - } - } - - .positive-hint { - color: $valid-value-color; - font-weight: 500; - } - - .negative-hint { - color: $error-value-color; - font-weight: 500; - } - - .neutral-hint { - color: $dark-text-color; - font-weight: 500; - } } @media screen and (max-width: $no-columns-breakpoint) { @@ -249,6 +226,41 @@ $content-width: 840px; } } +hr.spacer { + width: 100%; + border: 0; + margin: 20px 0; + height: 1px; +} + +.muted-hint { + color: $darker-text-color; + + a { + color: $highlight-text-color; + } +} + +.positive-hint { + color: $valid-value-color; + font-weight: 500; +} + +.negative-hint { + color: $error-value-color; + font-weight: 500; +} + +.neutral-hint { + color: $dark-text-color; + font-weight: 500; +} + +.warning-hint { + color: $gold-star; + font-weight: 500; +} + .filters { display: flex; flex-wrap: wrap; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 456ee4e0d..ac99124ea 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -300,6 +300,13 @@ code { } } + .input.static .label_input__wrapper { + font-size: 16px; + padding: 10px; + border: 1px solid $dark-text-color; + border-radius: 4px; + } + input[type=text], input[type=number], input[type=email], diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 85bc8eb1f..c9f78cd31 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -200,11 +200,7 @@ class Formatter def encode_custom_emojis(html, emojis, animate = false) return html if emojis.empty? - emoji_map = if animate - emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url) } - else - emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url(:static)) } - end + emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] } i = -1 tag_open_index = nil @@ -220,7 +216,14 @@ class Formatter emoji = emoji_map[shortcode] if emoji - replacement = "<img draggable=\"false\" class=\"emojione\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(emoji)}\" />" + original_url, static_url = emoji + replacement = begin + if animate + "<img draggable=\"false\" class=\"emojione\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(original_url)}\" />" + else + "<img draggable=\"false\" class=\"emojione custom-emoji\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(static_url)}\" data-original=\"#{original_url}\" data-static=\"#{static_url}\" />" + end + end before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : '' html = before_html + replacement + html[i + 1..-1] i += replacement.size - (shortcode.size + 2) - 1 diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index 2877b9c25..f76cf305f 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -31,9 +31,6 @@ module AccountAssociations has_many :media_attachments, dependent: :destroy has_many :polls, dependent: :destroy - # PuSH subscriptions - has_many :subscriptions, dependent: :destroy - # Report relationships has_many :reports, dependent: :destroy, inverse_of: :account has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb index 283033083..b9c124841 100644 --- a/app/models/concerns/omniauthable.rb +++ b/app/models/concerns/omniauthable.rb @@ -43,7 +43,7 @@ module Omniauthable # Check if the user exists with provided email if the provider gives us a # verified email. If no verified email was provided or the user already # exists, we assign a temporary email and ask the user to verify it on - # the next step via Auth::ConfirmationsController.finish_signup + # the next step via Auth::SetupController.show user = User.new(user_params_from_auth(auth)) user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/ diff --git a/app/models/subscription.rb b/app/models/subscription.rb deleted file mode 100644 index 79b81828d..000000000 --- a/app/models/subscription.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true -# == Schema Information -# -# Table name: subscriptions -# -# id :bigint(8) not null, primary key -# callback_url :string default(""), not null -# secret :string -# expires_at :datetime -# confirmed :boolean default(FALSE), not null -# created_at :datetime not null -# updated_at :datetime not null -# last_successful_delivery_at :datetime -# domain :string -# account_id :bigint(8) not null -# - -class Subscription < ApplicationRecord - MIN_EXPIRATION = 1.day.to_i - MAX_EXPIRATION = 30.days.to_i - - belongs_to :account - - validates :callback_url, presence: true - validates :callback_url, uniqueness: { scope: :account_id } - - scope :confirmed, -> { where(confirmed: true) } - scope :future_expiration, -> { where(arel_table[:expires_at].gt(Time.now.utc)) } - scope :expired, -> { where(arel_table[:expires_at].lt(Time.now.utc)) } - scope :active, -> { confirmed.future_expiration } - - def lease_seconds=(value) - self.expires_at = future_expiration(value) - end - - def lease_seconds - (expires_at - Time.now.utc).to_i - end - - def expired? - Time.now.utc > expires_at - end - - before_validation :set_min_expiration - - private - - def future_expiration(value) - Time.now.utc + future_offset(value).seconds - end - - def future_offset(seconds) - [ - [MIN_EXPIRATION, seconds.to_i].max, - MAX_EXPIRATION, - ].min - end - - def set_min_expiration - self.lease_seconds = 0 unless expires_at - end -end diff --git a/app/models/user.rb b/app/models/user.rb index 72fc92195..1548e1ea0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -161,7 +161,11 @@ class User < ApplicationRecord end def active_for_authentication? - super && approved? + true + end + + def functional? + confirmed? && approved? && !disabled? && !account.suspended? end def inactive_message diff --git a/app/policies/subscription_policy.rb b/app/policies/subscription_policy.rb deleted file mode 100644 index ac9a8a6c4..000000000 --- a/app/policies/subscription_policy.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class SubscriptionPolicy < ApplicationPolicy - def index? - admin? - end -end diff --git a/app/serializers/rest/web_push_subscription_serializer.rb b/app/serializers/rest/web_push_subscription_serializer.rb index 7fd952a56..194cc0a8c 100644 --- a/app/serializers/rest/web_push_subscription_serializer.rb +++ b/app/serializers/rest/web_push_subscription_serializer.rb @@ -4,7 +4,7 @@ class REST::WebPushSubscriptionSerializer < ActiveModel::Serializer attributes :id, :endpoint, :alerts, :server_key def alerts - object.data&.dig('alerts') || {} + (object.data&.dig('alerts') || {}).each_with_object({}) { |(k, v), h| h[k] = ActiveModel::Type::Boolean.new.cast(v) } end def server_key diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 0ebe0b562..00cffcdfc 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -24,7 +24,6 @@ class SuspendAccountService < BaseService report_notes scheduled_statuses status_pins - subscriptions ).freeze ASSOCIATIONS_ON_DESTROY = %w( diff --git a/app/views/auth/confirmations/finish_signup.html.haml b/app/views/auth/confirmations/finish_signup.html.haml deleted file mode 100644 index 9d09b74e1..000000000 --- a/app/views/auth/confirmations/finish_signup.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -- content_for :page_title do - = t('auth.confirm_email') - -= simple_form_for(current_user, as: 'user', url: finish_signup_path, html: { role: 'form'}) do |f| - - if @show_errors && current_user.errors.any? - #error_explanation - - current_user.errors.full_messages.each do |msg| - = msg - %br - - .fields-group - = f.input :email, wrapper: :with_label, required: true, hint: false - - .actions - = f.submit t('auth.confirm_email'), class: 'button' diff --git a/app/views/auth/registrations/_sessions.html.haml b/app/views/auth/registrations/_sessions.html.haml index d7d96a1bb..395e36a9f 100644 --- a/app/views/auth/registrations/_sessions.html.haml +++ b/app/views/auth/registrations/_sessions.html.haml @@ -1,6 +1,8 @@ -%h4= t 'sessions.title' +%h3= t 'sessions.title' %p.muted-hint= t 'sessions.explanation' +%hr.spacer/ + .table-wrapper %table.table.inline-table %thead diff --git a/app/views/auth/registrations/_status.html.haml b/app/views/auth/registrations/_status.html.haml new file mode 100644 index 000000000..b38a83d67 --- /dev/null +++ b/app/views/auth/registrations/_status.html.haml @@ -0,0 +1,16 @@ +%h3= t('auth.status.account_status') + +- if @user.account.suspended? + %span.negative-hint= t('user_mailer.warning.explanation.suspend') +- elsif @user.disabled? + %span.negative-hint= t('user_mailer.warning.explanation.disable') +- elsif @user.account.silenced? + %span.warning-hint= t('user_mailer.warning.explanation.silence') +- elsif !@user.confirmed? + %span.warning-hint= t('auth.status.confirming') +- elsif !@user.approved? + %span.warning-hint= t('auth.status.pending') +- else + %span.positive-hint= t('auth.status.functional') + +%hr.spacer/ diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index 694461fdf..710ee5c68 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -1,25 +1,28 @@ - content_for :page_title do - = t('auth.security') + = t('settings.account_settings') + += render 'status' + +%h3= t('auth.security') = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f| = render 'shared/error_messages', object: resource - if !use_seamless_external_login? || resource.encrypted_password.present? - .fields-group - = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, hint: false - - .fields-group - = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true - - .fields-group - = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: false - - .fields-group - = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' } - + .fields-row + .fields-row__column.fields-group.fields-row__column-6 + = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended? + .fields-row__column.fields-group.fields-row__column-6 + = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true, disabled: current_account.suspended? + + .fields-row + .fields-row__column.fields-group.fields-row__column-6 + = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended? + .fields-row__column.fields-group.fields-row__column-6 + = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, disabled: current_account.suspended? .actions - = f.button :button, t('generic.save_changes'), type: :submit + = f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended? - else %p.hint= t('users.seamless_external_login') @@ -27,7 +30,7 @@ = render 'sessions' -- if open_deletion? +- if open_deletion? && !current_account.suspended? %hr.spacer/ - %h4= t('auth.delete_account') + %h3= t('auth.delete_account') %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml new file mode 100644 index 000000000..8bb44ca7f --- /dev/null +++ b/app/views/auth/setup/show.html.haml @@ -0,0 +1,23 @@ +- content_for :page_title do + = t('auth.setup.title') + +- if missing_email? + = simple_form_for(@user, url: auth_setup_path) do |f| + = render 'shared/error_messages', object: @user + + .fields-group + %p.hint= t('auth.setup.email_below_hint_html') + + .fields-group + = f.input :email, required: true, hint: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' } + + .actions + = f.submit t('admin.accounts.change_email.label'), class: 'button' +- else + .simple_form + %p.hint= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email)) + +.form-footer + %ul.no-list + %li= link_to t('settings.account_settings'), edit_user_registration_path + %li= link_to t('auth.logout'), destroy_user_session_path, data: { method: :delete } diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml index 19af5f55d..7203d758d 100644 --- a/app/views/oauth/authorized_applications/index.html.haml +++ b/app/views/oauth/authorized_applications/index.html.haml @@ -17,7 +17,7 @@ = application.name - else = link_to application.name, application.website, target: '_blank', rel: 'noopener' - %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />') + %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join(', ') %td= l application.created_at %td - unless application.superapp? diff --git a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb index 5fba120f6..75fe681a9 100644 --- a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb +++ b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb @@ -5,7 +5,5 @@ class Scheduler::SubscriptionsCleanupScheduler sidekiq_options unique: :until_executed, retry: 0 - def perform - Subscription.expired.in_batches.delete_all - end + def perform; end end diff --git a/config/application.rb b/config/application.rb index 4534ede49..f49deffbb 100644 --- a/config/application.rb +++ b/config/application.rb @@ -114,6 +114,9 @@ module Mastodon Doorkeeper::AuthorizationsController.layout 'modal' Doorkeeper::AuthorizedApplicationsController.layout 'admin' Doorkeeper::Application.send :include, ApplicationExtension + Devise::FailureApp.send :include, AbstractController::Callbacks + Devise::FailureApp.send :include, HttpAcceptLanguage::EasyAccess + Devise::FailureApp.send :include, Localized end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index f05d69d36..6e611f2e5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -537,7 +537,6 @@ en: apply_for_account: Request an invite change_password: Password checkbox_agreement_html: I agree to the <a href="%{rules_path}" target="_blank">server rules</a> and <a href="%{terms_path}" target="_blank">terms of service</a> - confirm_email: Confirm email delete_account: Delete account delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation. didnt_get_confirmation: Didn't receive confirmation instructions? @@ -557,6 +556,14 @@ en: reset_password: Reset password security: Security set_new_password: Set new password + setup: + email_below_hint_html: If the below e-mail address is incorrect, you can change it here and receive a new confirmation e-mail. + email_settings_hint_html: The confirmation e-mail was sent to %{email}. If that e-mail address is not correct, you can change it in account settings. + title: Setup + status: + account_status: Account status + confirming: Waiting for e-mail confirmation to be completed. + pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved. trouble_logging_in: Trouble logging in? authorize_follow: already_following: You are already following this account diff --git a/config/routes.rb b/config/routes.rb index 66be635a5..418b66114 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,7 +34,10 @@ Rails.application.routes.draw do devise_scope :user do get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite - match '/auth/finish_signup' => 'auth/confirmations#finish_signup', via: [:get, :patch], as: :finish_signup + + namespace :auth do + resource :setup, only: [:show, :update], controller: :setup + end end devise_for :users, path: 'auth', controllers: { diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 5c652792c..7f41b6607 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -21,9 +21,6 @@ user_cleanup_scheduler: cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *' class: Scheduler::UserCleanupScheduler - subscriptions_cleanup_scheduler: - cron: '<%= Random.rand(0..59) %> <%= Random.rand(1..3) %> * * 0' - class: Scheduler::SubscriptionsCleanupScheduler ip_cleanup_scheduler: cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' class: Scheduler::IpCleanupScheduler diff --git a/db/post_migrate/20190715031050_drop_subscriptions.rb b/db/post_migrate/20190715031050_drop_subscriptions.rb new file mode 100644 index 000000000..3719afe4a --- /dev/null +++ b/db/post_migrate/20190715031050_drop_subscriptions.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class DropSubscriptions < ActiveRecord::Migration[5.2] + def up + drop_table :subscriptions + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 160087847..b7da26e35 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -661,19 +661,6 @@ ActiveRecord::Schema.define(version: 2019_07_15_164535) do t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true end - create_table "subscriptions", force: :cascade do |t| - t.string "callback_url", default: "", null: false - t.string "secret" - t.datetime "expires_at" - t.boolean "confirmed", default: false, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.datetime "last_successful_delivery_at" - t.string "domain" - t.bigint "account_id", null: false - t.index ["account_id", "callback_url"], name: "index_subscriptions_on_account_id_and_callback_url", unique: true - end - create_table "tags", force: :cascade do |t| t.string "name", default: "", null: false t.datetime "created_at", null: false @@ -836,7 +823,6 @@ ActiveRecord::Schema.define(version: 2019_07_15_164535) do add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade add_foreign_key "statuses_tags", "statuses", on_delete: :cascade add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade - add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade add_foreign_key "tombstones", "accounts", on_delete: :cascade add_foreign_key "user_invite_requests", "users", on_delete: :cascade add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade diff --git a/db/seeds.rb b/db/seeds.rb index 5f43fbac8..0bfb5d0db 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,8 @@ -Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow') +Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push') domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain -Account.create!(id: -99, actor_type: 'Application', locked: true, username: domain) +account = Account.find_or_initialize_by(id: -99, actor_type: 'Application', locked: true, username: domain) +account.save! if Rails.env.development? admin = Account.where(username: 'admin').first_or_initialize(username: 'admin') diff --git a/package.json b/package.json index a56cc75ea..aa9d809f1 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@babel/preset-env": "^7.4.5", "@babel/preset-react": "^7.0.0", "@babel/runtime": "^7.5.4", - "@clusterws/cws": "^0.14.0", + "@clusterws/cws": "^0.15.0", "array-includes": "^3.0.3", "atrament": "^0.2.3", "autoprefixer": "^9.6.0", @@ -106,7 +106,7 @@ "intersection-observer": "^0.7.0", "intl": "^1.2.5", "intl-messageformat": "^2.2.0", - "intl-relativeformat": "^6.4.2", + "intl-relativeformat": "^6.4.3", "is-nan": "^1.2.1", "js-yaml": "^3.13.1", "lodash": "^4.17.14", @@ -169,11 +169,11 @@ "websocket.js": "^0.1.12" }, "devDependencies": { - "babel-eslint": "^10.0.1", + "babel-eslint": "^10.0.2", "babel-jest": "^24.8.0", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", - "eslint": "^5.16.0", + "eslint": "^6.1.0", "eslint-plugin-import": "~2.18.0", "eslint-plugin-jsx-a11y": "~6.2.3", "eslint-plugin-promise": "~4.2.1", diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index 750ccc8cf..05a42d1c1 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::BaseController do end end - describe 'Forgery protection' do + describe 'forgery protection' do before do routes.draw { post 'success' => 'api/base#success' } end @@ -27,7 +27,45 @@ describe Api::BaseController do end end - describe 'Error handling' do + describe 'non-functional accounts handling' do + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') } + + controller do + before_action :require_user! + end + + before do + routes.draw { post 'success' => 'api/base#success' } + allow(controller).to receive(:doorkeeper_token) { token } + end + + it 'returns http forbidden for unconfirmed accounts' do + user.update(confirmed_at: nil) + post 'success' + expect(response).to have_http_status(403) + end + + it 'returns http forbidden for pending accounts' do + user.update(approved: false) + post 'success' + expect(response).to have_http_status(403) + end + + it 'returns http forbidden for disabled accounts' do + user.update(disabled: true) + post 'success' + expect(response).to have_http_status(403) + end + + it 'returns http forbidden for suspended accounts' do + user.account.suspend! + post 'success' + expect(response).to have_http_status(403) + end + end + + describe 'error handling' do ERRORS_WITH_CODES = { ActiveRecord::RecordInvalid => 422, Mastodon::ValidationError => 422, diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 99015c82d..67d3c1ce9 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -191,10 +191,10 @@ describe ApplicationController, type: :controller do expect(response).to have_http_status(200) end - it 'returns http 403 if user who signed in is suspended' do + it 'redirects to account status page' do sign_in(Fabricate(:user, account: Fabricate(:account, suspended: true))) get 'success' - expect(response).to have_http_status(403) + expect(response).to redirect_to(edit_user_registration_path) end end diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb index e9a471fc5..0b6b74ff9 100644 --- a/spec/controllers/auth/confirmations_controller_spec.rb +++ b/spec/controllers/auth/confirmations_controller_spec.rb @@ -50,45 +50,4 @@ describe Auth::ConfirmationsController, type: :controller do end end end - - describe 'GET #finish_signup' do - subject { get :finish_signup } - - let(:user) { Fabricate(:user) } - before do - sign_in user, scope: :user - @request.env['devise.mapping'] = Devise.mappings[:user] - end - - it 'renders finish_signup' do - is_expected.to render_template :finish_signup - expect(assigns(:user)).to have_attributes id: user.id - end - end - - describe 'PATCH #finish_signup' do - subject { patch :finish_signup, params: { user: { email: email } } } - - let(:user) { Fabricate(:user) } - before do - sign_in user, scope: :user - @request.env['devise.mapping'] = Devise.mappings[:user] - end - - context 'when email is valid' do - let(:email) { 'new_' + user.email } - - it 'redirects to root_path' do - is_expected.to redirect_to root_path - end - end - - context 'when email is invalid' do - let(:email) { '' } - - it 'renders finish_signup' do - is_expected.to render_template :finish_signup - end - end - end end diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index a4337039e..3e11b34b5 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -46,6 +46,15 @@ RSpec.describe Auth::RegistrationsController, type: :controller do post :update expect(response).to have_http_status(200) end + + context 'when suspended' do + it 'returns http forbidden' do + request.env["devise.mapping"] = Devise.mappings[:user] + sign_in(Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }), scope: :user) + post :update + expect(response).to have_http_status(403) + end + end end describe 'GET #new' do @@ -94,9 +103,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } } end - it 'redirects to login page' do + it 'redirects to setup' do subject - expect(response).to redirect_to new_user_session_path + expect(response).to redirect_to auth_setup_path end it 'creates user' do @@ -120,9 +129,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } } end - it 'redirects to login page' do + it 'redirects to setup' do subject - expect(response).to redirect_to new_user_session_path + expect(response).to redirect_to auth_setup_path end it 'creates user' do @@ -148,9 +157,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } } end - it 'redirects to login page' do + it 'redirects to setup' do subject - expect(response).to redirect_to new_user_session_path + expect(response).to redirect_to auth_setup_path end it 'creates user' do @@ -176,9 +185,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } } end - it 'redirects to login page' do + it 'redirects to setup' do subject - expect(response).to redirect_to new_user_session_path + expect(response).to redirect_to auth_setup_path end it 'creates user' do diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index 71fcc1a6e..87ef4f2bb 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -160,8 +160,8 @@ RSpec.describe Auth::SessionsController, type: :controller do let(:unconfirmed_user) { user.tap { |u| u.update!(confirmed_at: nil) } } let(:accept_language) { 'fr' } - it 'shows a translated login error' do - expect(flash[:alert]).to eq(I18n.t('devise.failure.unconfirmed', locale: accept_language)) + it 'redirects to home' do + expect(response).to redirect_to(root_path) end end diff --git a/spec/controllers/concerns/localized_spec.rb b/spec/controllers/concerns/localized_spec.rb index 76c3de118..7635d10e1 100644 --- a/spec/controllers/concerns/localized_spec.rb +++ b/spec/controllers/concerns/localized_spec.rb @@ -7,16 +7,10 @@ describe ApplicationController, type: :controller do include Localized def success - head 200 + render plain: I18n.locale, status: 200 end end - around do |example| - current_locale = I18n.locale - example.run - I18n.locale = current_locale - end - before do routes.draw { get 'success' => 'anonymous#success' } end @@ -25,19 +19,19 @@ describe ApplicationController, type: :controller do it 'sets available and preferred language' do request.headers['Accept-Language'] = 'ca-ES, fa' get 'success' - expect(I18n.locale).to eq :fa + expect(response.body).to eq 'fa' end it 'sets available and compatible language if none of available languages are preferred' do request.headers['Accept-Language'] = 'fa-IR' get 'success' - expect(I18n.locale).to eq :fa + expect(response.body).to eq 'fa' end it 'sets default locale if none of available languages are compatible' do request.headers['Accept-Language'] = '' get 'success' - expect(I18n.locale).to eq :en + expect(response.body).to eq 'en' end end @@ -48,7 +42,7 @@ describe ApplicationController, type: :controller do sign_in(user) get 'success' - expect(I18n.locale).to eq :ca + expect(response.body).to eq 'ca' end end diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb index 35fd64e9b..996872efd 100644 --- a/spec/controllers/settings/deletes_controller_spec.rb +++ b/spec/controllers/settings/deletes_controller_spec.rb @@ -15,6 +15,15 @@ describe Settings::DeletesController do get :show expect(response).to have_http_status(200) end + + context 'when suspended' do + let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) } + + it 'returns http forbidden' do + get :show + expect(response).to have_http_status(403) + end + end end context 'when not signed in' do @@ -49,6 +58,14 @@ describe Settings::DeletesController do it 'marks account as suspended' do expect(user.account.reload).to be_suspended end + + context 'when suspended' do + let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end end context 'with incorrect password' do diff --git a/spec/fabricators/subscription_fabricator.rb b/spec/fabricators/subscription_fabricator.rb deleted file mode 100644 index 347dab5df..000000000 --- a/spec/fabricators/subscription_fabricator.rb +++ /dev/null @@ -1,7 +0,0 @@ -Fabricator(:subscription) do - account - callback_url "http://example.com/callback" - secret "foobar" - expires_at "2016-11-28 11:30:07" - confirmed false -end diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb index 53a1f9b12..f6c26cd0f 100644 --- a/spec/features/log_in_spec.rb +++ b/spec/features/log_in_spec.rb @@ -31,12 +31,12 @@ feature "Log in" do context do given(:confirmed_at) { nil } - scenario "A unconfirmed user is not able to log in" do + scenario "A unconfirmed user is able to log in" do fill_in "user_email", with: email fill_in "user_password", with: password click_on I18n.t('auth.login') - is_expected.to have_css(".flash-message", text: failure_message("unconfirmed")) + is_expected.to have_css("div.admin-wrapper") end end diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 96d2fc7e0..b8108a247 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -261,7 +261,7 @@ RSpec.describe Formatter do let(:text) { ':coolcat: Beep boop' } it 'converts the shortcode to an image tag' do - is_expected.to match(/<img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/<img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end end @@ -330,7 +330,7 @@ RSpec.describe Formatter do let(:text) { ':coolcat: Beep boop' } it 'converts the shortcode to an image tag' do - is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -338,7 +338,7 @@ RSpec.describe Formatter do let(:text) { 'Beep :coolcat: boop' } it 'converts the shortcode to an image tag' do - is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -354,7 +354,7 @@ RSpec.describe Formatter do let(:text) { 'Beep boop :coolcat:' } it 'converts the shortcode to an image tag' do - is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end end @@ -377,7 +377,7 @@ RSpec.describe Formatter do let(:text) { '<p>:coolcat: Beep boop<br />' } it 'converts the shortcode to an image tag' do - is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -385,7 +385,7 @@ RSpec.describe Formatter do let(:text) { '<p>Beep :coolcat: boop</p>' } it 'converts the shortcode to an image tag' do - is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -401,7 +401,7 @@ RSpec.describe Formatter do let(:text) { '<p>Beep boop<br />:coolcat:</p>' } it 'converts the shortcode to an image tag' do - is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end end @@ -500,7 +500,7 @@ RSpec.describe Formatter do let(:text) { ':coolcat: Beep boop' } it 'converts the shortcode to an image tag' do - is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -508,7 +508,7 @@ RSpec.describe Formatter do let(:text) { 'Beep :coolcat: boop' } it 'converts the shortcode to an image tag' do - is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -524,7 +524,7 @@ RSpec.describe Formatter do let(:text) { 'Beep boop :coolcat:' } it 'converts the shortcode to an image tag' do - is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end end @@ -551,7 +551,7 @@ RSpec.describe Formatter do let(:text) { '<p>:coolcat: Beep boop<br />' } it 'converts shortcode to image tag' do - is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -559,7 +559,7 @@ RSpec.describe Formatter do let(:text) { '<p>Beep :coolcat: boop</p>' } it 'converts shortcode to image tag' do - is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end @@ -575,7 +575,7 @@ RSpec.describe Formatter do let(:text) { '<p>Beep boop<br />:coolcat:</p>' } it 'converts shortcode to image tag' do - is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/) + is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/) end end end diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb deleted file mode 100644 index b83979d13..000000000 --- a/spec/models/subscription_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'rails_helper' - -RSpec.describe Subscription, type: :model do - let(:alice) { Fabricate(:account, username: 'alice') } - - subject { Fabricate(:subscription, account: alice) } - - describe '#expired?' do - it 'return true when expires_at is past' do - subject.expires_at = 2.days.ago - expect(subject.expired?).to be true - end - - it 'return false when expires_at is future' do - subject.expires_at = 2.days.from_now - expect(subject.expired?).to be false - end - end - - describe 'lease_seconds' do - it 'returns the time remaining until expiration' do - datetime = 1.day.from_now - subscription = Subscription.new(expires_at: datetime) - travel_to(datetime - 12.hours) do - expect(subscription.lease_seconds).to eq(12.hours) - end - end - end - - describe 'lease_seconds=' do - it 'sets expires_at to min expiration when small value is provided' do - subscription = Subscription.new - datetime = 1.day.from_now - too_low = Subscription::MIN_EXPIRATION - 1000 - travel_to(datetime) do - subscription.lease_seconds = too_low - end - - expected = datetime + Subscription::MIN_EXPIRATION.seconds - expect(subscription.expires_at).to be_within(1.0).of(expected) - end - - it 'sets expires_at to value when valid value is provided' do - subscription = Subscription.new - datetime = 1.day.from_now - valid = Subscription::MIN_EXPIRATION + 1000 - travel_to(datetime) do - subscription.lease_seconds = valid - end - - expected = datetime + valid.seconds - expect(subscription.expires_at).to be_within(1.0).of(expected) - end - - it 'sets expires_at to max expiration when large value is provided' do - subscription = Subscription.new - datetime = 1.day.from_now - too_high = Subscription::MAX_EXPIRATION + 1000 - travel_to(datetime) do - subscription.lease_seconds = too_high - end - - expected = datetime + Subscription::MAX_EXPIRATION.seconds - expect(subscription.expires_at).to be_within(1.0).of(expected) - end - end -end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 856254ce4..d7c0b5359 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -506,7 +506,7 @@ RSpec.describe User, type: :model do context 'when user is not confirmed' do let(:confirmed_at) { nil } - it { is_expected.to be false } + it { is_expected.to be true } end end @@ -522,7 +522,7 @@ RSpec.describe User, type: :model do context 'when user is not confirmed' do let(:confirmed_at) { nil } - it { is_expected.to be false } + it { is_expected.to be true } end end end diff --git a/spec/policies/subscription_policy_spec.rb b/spec/policies/subscription_policy_spec.rb deleted file mode 100644 index 21d60c15f..000000000 --- a/spec/policies/subscription_policy_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' -require 'pundit/rspec' - -RSpec.describe SubscriptionPolicy do - let(:subject) { described_class } - let(:admin) { Fabricate(:user, admin: true).account } - let(:john) { Fabricate(:user).account } - - permissions :index? do - context 'admin?' do - it 'permits' do - expect(subject).to permit(admin, Subscription) - end - end - - context '!admin?' do - it 'denies' do - expect(subject).to_not permit(john, Subscription) - end - end - end -end diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb index d52e7f484..f84256f18 100644 --- a/spec/services/batched_remove_status_service_spec.rb +++ b/spec/services/batched_remove_status_service_spec.rb @@ -14,11 +14,8 @@ RSpec.describe BatchedRemoveStatusService, type: :service do before do allow(Redis.current).to receive_messages(publish: nil) - stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {}) - stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {}) stub_request(:post, 'http://example.com/inbox').to_return(status: 200) - Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now) jeff.user.update(current_sign_in_at: Time.zone.now) jeff.follow!(alice) hank.follow!(alice) diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb index 48191d47c..06676ec45 100644 --- a/spec/services/remove_status_service_spec.rb +++ b/spec/services/remove_status_service_spec.rb @@ -10,12 +10,9 @@ RSpec.describe RemoveStatusService, type: :service do let!(:bill) { Fabricate(:account, username: 'bill', protocol: :activitypub, domain: 'example2.com', inbox_url: 'http://example2.com/inbox') } before do - stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {}) - stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {}) stub_request(:post, 'http://example.com/inbox').to_return(status: 200) stub_request(:post, 'http://example2.com/inbox').to_return(status: 200) - Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now) jeff.follow!(alice) hank.follow!(alice) diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb index 896ac17a3..eebbbc12a 100644 --- a/spec/services/suspend_account_service_spec.rb +++ b/spec/services/suspend_account_service_spec.rb @@ -18,7 +18,6 @@ RSpec.describe SuspendAccountService, type: :service do let!(:favourite) { Fabricate(:favourite, account: account) } let!(:active_relationship) { Fabricate(:follow, account: account) } let!(:passive_relationship) { Fabricate(:follow, target_account: account) } - let!(:subscription) { Fabricate(:subscription, account: account) } let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } @@ -31,9 +30,8 @@ RSpec.describe SuspendAccountService, type: :service do account.favourites, account.active_relationships, account.passive_relationships, - account.subscriptions ].map(&:count) - }.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0]) + }.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0]) end it 'sends a delete actor activity to all known inboxes' do @@ -62,7 +60,6 @@ RSpec.describe SuspendAccountService, type: :service do let!(:favourite) { Fabricate(:favourite, account: remote_bob) } let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) } let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) } - let!(:subscription) { Fabricate(:subscription, account: remote_bob) } it 'deletes associated records' do is_expected.to change { @@ -73,9 +70,8 @@ RSpec.describe SuspendAccountService, type: :service do remote_bob.favourites, remote_bob.active_relationships, remote_bob.passive_relationships, - remote_bob.subscriptions ].map(&:count) - }.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0]) + }.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0]) end it 'sends a reject follow to follwer inboxes' do diff --git a/yarn.lock b/yarn.lock index 63badbec1..8a8fd8d78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,7 +60,7 @@ source-map "^0.5.0" trim-right "^1.0.1" -"@babel/generator@^7.2.2", "@babel/generator@^7.4.4": +"@babel/generator@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ== @@ -229,7 +229,7 @@ "@babel/template" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.4": +"@babel/helper-split-export-declaration@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== @@ -273,17 +273,7 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.3.tgz#32f5df65744b70888d17872ec106b02434ba1489" - integrity sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA== - -"@babel/parser@^7.1.0", "@babel/parser@^7.3.4": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" - integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== - -"@babel/parser@^7.2.2", "@babel/parser@^7.2.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew== @@ -793,22 +783,7 @@ "@babel/parser" "^7.4.4" "@babel/types" "^7.4.4" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.5": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" - integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.2.2" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.2.3" - "@babel/types" "^7.2.2" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.10" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5": +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.3.4", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216" integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A== @@ -823,22 +798,7 @@ globals "^11.1.0" lodash "^4.17.11" -"@babel/traverse@^7.3.4": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06" - integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.3.4" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.3.4" - "@babel/types" "^7.3.4" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.11" - -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4", "@babel/types@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ== @@ -847,28 +807,10 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0-beta.49": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e" - integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg== - dependencies: - esutils "^2.0.2" - lodash "^4.17.10" - to-fast-properties "^2.0.0" - -"@babel/types@^7.3.0", "@babel/types@^7.3.4": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed" - integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ== - dependencies: - esutils "^2.0.2" - lodash "^4.17.11" - to-fast-properties "^2.0.0" - -"@clusterws/cws@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.14.0.tgz#242824b6884454001340222a836db6f6c5e62bfb" - integrity sha512-knZj3KZNHIAGsX7TUc/0Q5gcx2bKMMcTPsAOZomLKdK5a4o/umKFlttWRH84Yr1nVlQy+UMO23qfDR8gRZ/4cw== +"@clusterws/cws@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.15.0.tgz#1d585927252d1cd92e676c952fa6d69df14a0d07" + integrity sha512-41QpCngw86n41hIdU5Nx2QJmmxZuA9FPtDkjONrYpk27L7HjL1kj6J5oWEjbr14yXLfigZit3VY+oACDCGbiHw== "@cnakazawa/watch@^1.0.3": version "1.0.3" @@ -1423,10 +1365,10 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1: - version "6.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" - integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.5.5, ajv@^6.9.1: + version "6.10.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" + integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -1728,10 +1670,10 @@ axobject-query@^2.0.2: dependencies: ast-types-flow "0.0.7" -babel-eslint@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" - integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ== +babel-eslint@^10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456" + integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q== dependencies: "@babel/code-frame" "^7.0.0" "@babel/parser" "^7.0.0" @@ -3709,7 +3651,7 @@ eslint-scope@3.7.1: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^4.0.0, eslint-scope@^4.0.3: +eslint-scope@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== @@ -3717,6 +3659,14 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-utils@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" @@ -3766,47 +3716,48 @@ eslint@^2.7.0: text-table "~0.2.0" user-home "^2.0.0" -eslint@^5.16.0: - version "5.16.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" - integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== +eslint@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.1.0.tgz#06438a4a278b1d84fb107d24eaaa35471986e646" + integrity sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ== dependencies: "@babel/code-frame" "^7.0.0" - ajv "^6.9.1" + ajv "^6.10.0" chalk "^2.1.0" cross-spawn "^6.0.5" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^4.0.3" + eslint-scope "^5.0.0" eslint-utils "^1.3.1" eslint-visitor-keys "^1.0.0" - espree "^5.0.1" + espree "^6.0.0" esquery "^1.0.1" esutils "^2.0.2" file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" - glob "^7.1.2" + glob-parent "^5.0.0" globals "^11.7.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.2.2" - js-yaml "^3.13.0" + inquirer "^6.4.1" + is-glob "^4.0.0" + js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" - lodash "^4.17.11" + lodash "^4.17.14" minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" optionator "^0.8.2" - path-is-inside "^1.0.2" progress "^2.0.0" regexpp "^2.0.1" - semver "^5.5.1" - strip-ansi "^4.0.0" - strip-json-comments "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" table "^5.2.3" text-table "^0.2.0" + v8-compile-cache "^2.0.3" espree@^3.1.6: version "3.5.4" @@ -3816,10 +3767,10 @@ espree@^3.1.6: acorn "^5.5.0" acorn-jsx "^3.0.0" -espree@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" - integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== +espree@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.0.0.tgz#716fc1f5a245ef5b9a7fdb1d7b0d3f02322e75f6" + integrity sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q== dependencies: acorn "^6.0.7" acorn-jsx "^5.0.0" @@ -4481,6 +4432,13 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob-parent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" + integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== + dependencies: + is-glob "^4.0.1" + glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" @@ -5027,10 +4985,10 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -inquirer@^6.2.2: - version "6.4.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.4.1.tgz#7bd9e5ab0567cd23b41b0180b68e0cfa82fc3c0b" - integrity sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw== +inquirer@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42" + integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA== dependencies: ansi-escapes "^3.2.0" chalk "^2.4.2" @@ -5038,7 +4996,7 @@ inquirer@^6.2.2: cli-width "^2.0.0" external-editor "^3.0.3" figures "^2.0.0" - lodash "^4.17.11" + lodash "^4.17.12" mute-stream "0.0.7" run-async "^2.2.0" rxjs "^6.4.0" @@ -5093,10 +5051,10 @@ intl-relativeformat@^2.1.0: dependencies: intl-messageformat "^2.0.0" -intl-relativeformat@^6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.2.tgz#431f9818449f5b48c209610ff1428d0c663c667f" - integrity sha512-yaOimRUQEn1wOfVGk43H+EVCrxQ5WFEvtYBx4Ffa6QpEHIi6UOuvshx6RltuqIF5UM8xdF4SkzFHXXOnYXlgBA== +intl-relativeformat@^6.4.3: + version "6.4.3" + resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.3.tgz#cb5559e1e257cc2e763583502012a354bb777efe" + integrity sha512-VxZXZfhuX/zBVfxzE/J6kPUpsyWKYjqtZ3jVGZwr6wzK5BOLVpe1vSlwCQX56w5UjlpL63fS8Nxq0kgTyf1gJA== intl@^1.2.5: version "1.2.5" @@ -5295,10 +5253,10 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" @@ -5896,7 +5854,7 @@ js-string-escape@1.0.1: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0: +js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -6260,10 +6218,10 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10: - version "4.17.14" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" - integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== +lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== loglevel@^1.6.3: version "1.6.3" @@ -9024,15 +8982,10 @@ semver@4.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= -semver@^6.0.0: - version "6.1.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.3.tgz#ef997a1a024f67dd48a7f155df88bb7b5c6c3fc7" - integrity sha512-aymF+56WJJMyXQHcd4hlK4N75rwj5RQpfW8ePlQnJsTYOBLlLbcIErR/G1s9SkIvKBqOudR3KAx4wEqP+F1hNQ== - -semver@^6.1.0, semver@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b" - integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ== +semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2: + version "6.2.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db" + integrity sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A== send@0.17.1: version "0.17.1" @@ -9560,16 +9513,21 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +strip-json-comments@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== strip-json-comments@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + stylehacks@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.1.tgz#3186595d047ab0df813d213e51c8b94e0b9010f2" @@ -10096,7 +10054,7 @@ uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -v8-compile-cache@2.0.3: +v8-compile-cache@2.0.3, v8-compile-cache@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== |