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

Conflicts:
- app/controllers/application_controller.rb
- app/controllers/auth/confirmations_controller.rb
- app/controllers/auth/sessions_controller.rb
- app/controllers/settings/deletes_controller.rb
- app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
This commit is contained in:
Thibaut Girka 2019-07-23 10:17:06 +02:00
commit 444796b69b
76 changed files with 662 additions and 590 deletions

View File

@ -62,7 +62,7 @@ gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532' gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
gem 'nokogiri', '~> 1.10' gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2' gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.7' gem 'oj', '~> 3.8'
gem 'ostatus2', '~> 2.0' gem 'ostatus2', '~> 2.0'
gem 'ox', '~> 2.11' gem 'ox', '~> 2.11'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
@ -112,7 +112,7 @@ group :production, :test do
end end
group :test do group :test do
gem 'capybara', '~> 3.25' gem 'capybara', '~> 3.26'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
gem 'faker', '~> 1.9' gem 'faker', '~> 1.9'
gem 'microformats', '~> 4.1' gem 'microformats', '~> 4.1'
@ -132,7 +132,7 @@ group :development do
gem 'letter_opener', '~> 1.7' gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3' gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler' gem 'memory_profiler'
gem 'rubocop', '~> 0.72', require: false gem 'rubocop', '~> 0.73', require: false
gem 'rubocop-rails', '~> 2.2', require: false gem 'rubocop-rails', '~> 2.2', require: false
gem 'brakeman', '~> 4.5', require: false gem 'brakeman', '~> 4.5', require: false
gem 'bundler-audit', '~> 0.6', require: false gem 'bundler-audit', '~> 0.6', require: false

View File

@ -111,7 +111,7 @@ GEM
bootsnap (1.4.4) bootsnap (1.4.4)
msgpack (~> 1.0) msgpack (~> 1.0)
brakeman (4.5.1) brakeman (4.5.1)
browser (2.5.3) browser (2.6.1)
builder (3.2.3) builder (3.2.3)
bullet (6.0.1) bullet (6.0.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
@ -136,7 +136,7 @@ GEM
sshkit (~> 1.3) sshkit (~> 1.3)
capistrano-yarn (2.0.2) capistrano-yarn (2.0.2)
capistrano (~> 3.0) capistrano (~> 3.0)
capybara (3.25.0) capybara (3.26.0)
addressable addressable
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -163,7 +163,7 @@ GEM
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.4) crass (1.0.4)
css_parser (1.6.0) css_parser (1.7.0)
addressable addressable
debug_inspector (0.0.3) debug_inspector (0.0.3)
derailed_benchmarks (1.3.6) derailed_benchmarks (1.3.6)
@ -354,7 +354,7 @@ GEM
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812) mime-types-data (3.2018.0812)
mimemagic (0.3.3) mimemagic (0.3.3)
mini_mime (1.0.1) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.11.3) minitest (5.11.3)
msgpack (1.2.10) msgpack (1.2.10)
@ -375,7 +375,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5) sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0) statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.7.12) oj (3.8.0)
omniauth (1.9.0) omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0) hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
@ -417,8 +417,8 @@ GEM
addressable addressable
css_parser (>= 1.6.0) css_parser (>= 1.6.0)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
premailer-rails (1.10.2) premailer-rails (1.10.3)
actionmailer (>= 3, < 6) actionmailer (>= 3)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0) private_address_check (0.5.0)
pry (0.12.2) pry (0.12.2)
@ -505,7 +505,7 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.5.0) redis-store (1.5.0)
redis (>= 2.2, < 5) redis (>= 2.2, < 5)
regexp_parser (1.5.1) regexp_parser (1.6.0)
request_store (1.4.1) request_store (1.4.1)
rack (>= 1.4) rack (>= 1.4)
responders (2.4.1) responders (2.4.1)
@ -535,7 +535,7 @@ GEM
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0) sidekiq (>= 2.4.0)
rspec-support (3.8.0) rspec-support (3.8.0)
rubocop (0.72.0) rubocop (0.73.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.6) parser (>= 2.6)
@ -671,7 +671,7 @@ DEPENDENCIES
capistrano-rails (~> 1.4) capistrano-rails (~> 1.4)
capistrano-rbenv (~> 2.1) capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0) capistrano-yarn (~> 2.0)
capybara (~> 3.25) capybara (~> 3.26)
charlock_holmes (~> 0.7.6) charlock_holmes (~> 0.7.6)
chewy (~> 5.0) chewy (~> 5.0)
cld3 (~> 3.2.4) cld3 (~> 3.2.4)
@ -719,7 +719,7 @@ DEPENDENCIES
nilsimsa! nilsimsa!
nokogiri (~> 1.10) nokogiri (~> 1.10)
nsa (~> 0.2) nsa (~> 0.2)
oj (~> 3.7) oj (~> 3.8)
omniauth (~> 1.9) omniauth (~> 1.9)
omniauth-cas (~> 1.1) omniauth-cas (~> 1.1)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
@ -752,7 +752,7 @@ DEPENDENCIES
rqrcode (~> 0.10) rqrcode (~> 0.10)
rspec-rails (~> 3.8) rspec-rails (~> 3.8)
rspec-sidekiq (~> 3.0) rspec-sidekiq (~> 3.0)
rubocop (~> 0.72) rubocop (~> 0.73)
rubocop-rails (~> 2.2) rubocop-rails (~> 2.2)
sanitize (~> 5.0) sanitize (~> 5.0)
sidekiq (~> 5.2) sidekiq (~> 5.2)

View File

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

View File

@ -8,7 +8,7 @@ class AboutController < ApplicationController
before_action :set_instance_presenter before_action :set_instance_presenter
before_action :set_expires_in 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 def show; end

View File

@ -42,7 +42,7 @@ class AccountsController < ApplicationController
format.json do format.json do
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?) 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 end
end end

View File

@ -11,7 +11,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def show def show
expires_in 3.minutes, public: public_fetch_mode? 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 end
private private

View File

@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController
include RateLimitHeaders include RateLimitHeaders
skip_before_action :store_current_location skip_before_action :store_current_location
skip_before_action :check_user_permissions skip_before_action :require_functional!
before_action :set_cache_headers before_action :set_cache_headers

View File

@ -6,8 +6,7 @@ class Api::V1::CustomEmojisController < Api::BaseController
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
def index def index
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do expires_in 3.minutes, public: true
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false).includes(:category), each_serializer: REST::CustomEmojiSerializer) render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.local.where(disabled: false).includes(:category) }
end
end end
end end

View File

@ -7,7 +7,8 @@ class Api::V1::Instances::ActivityController < Api::BaseController
respond_to :json respond_to :json
def show 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 end
private private

View File

@ -7,7 +7,8 @@ class Api::V1::Instances::PeersController < Api::BaseController
respond_to :json respond_to :json
def index 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 end
private private

View File

@ -5,8 +5,7 @@ class Api::V1::InstancesController < Api::BaseController
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
def show def show
render_cached_json('api:v1:instances', expires_in: 5.minutes) do expires_in 3.minutes, public: true
ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer) render_with_cache json: {}, serializer: REST::InstanceSerializer
end
end end
end end

View File

@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base
include Localized include Localized
include UserTrackingConcern include UserTrackingConcern
include SessionTrackingConcern include SessionTrackingConcern
include CacheConcern
helper_method :current_account helper_method :current_account
helper_method :current_session helper_method :current_session
@ -25,7 +26,7 @@ class ApplicationController < ActionController::Base
rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from Mastodon::NotPermittedError, with: :forbidden
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? 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 def raise_not_found
raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}" raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
@ -57,8 +58,8 @@ class ApplicationController < ActionController::Base
forbidden unless current_user&.staff? forbidden unless current_user&.staff?
end end
def check_user_permissions def require_functional!
forbidden if current_user.disabled? || current_user.account.suspended? redirect_to edit_user_registration_path unless current_user.functional?
end end
def after_sign_out_path_for(_resource_or_scope) def after_sign_out_path_for(_resource_or_scope)
@ -190,52 +191,14 @@ class ApplicationController < ActionController::Base
current_user.setting_skin current_user.setting_skin
end 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) def respond_with_error(code)
respond_to do |format| respond_to do |format|
format.any { head code } format.any { head code }
format.html do format.html do
set_locale
use_pack 'error' use_pack 'error'
render "errors/#{code}", layout: 'error', status: code render "errors/#{code}", layout: 'error', status: code
end end
end 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 end

View File

@ -4,20 +4,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth' layout 'auth'
before_action :set_body_classes before_action :set_body_classes
before_action :set_user, only: [:finish_signup]
before_action :set_pack before_action :set_pack
def finish_signup skip_before_action :require_functional!
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
private private
@ -25,18 +14,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
use_pack 'auth' use_pack 'auth'
end end
def set_user
@user = current_user
end
def set_body_classes def set_body_classes
@body_classes = 'lighter' @body_classes = 'lighter'
end end
def user_params
params.require(:user).permit(:email)
end
def after_confirmation_path_for(_resource_name, user) def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && truthy_param?(:redirect_to_app) if user.created_by_application && truthy_param?(:redirect_to_app)
user.created_by_application.redirect_uri user.created_by_application.redirect_uri

View File

@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
if resource.email_verified? if resource.email_verified?
root_path root_path
else else
finish_signup_path auth_setup_path(missing_email: '1')
end end
end end
end end

View File

@ -10,6 +10,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_sessions, only: [:edit, :update] before_action :set_sessions, only: [:edit, :update]
before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create, :edit, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update]
skip_before_action :require_functional!, only: [:edit, :update]
def new def new
super(&:build_invite_request) super(&:build_invite_request)
@ -44,7 +47,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end end
def after_sign_up_path_for(_resource) def after_sign_up_path_for(_resource)
new_user_session_path auth_setup_path
end end
def after_sign_in_path_for(_resource) def after_sign_in_path_for(_resource)
@ -107,4 +110,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def set_sessions def set_sessions
@sessions = current_user.session_activations @sessions = current_user.session_activations
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View File

@ -6,9 +6,11 @@ class Auth::SessionsController < Devise::SessionsController
layout 'auth' layout 'auth'
skip_before_action :require_no_authentication, only: [:create] 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 :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
prepend_before_action :set_pack prepend_before_action :set_pack
before_action :set_instance_presenter, only: [:new] before_action :set_instance_presenter, only: [:new]
before_action :set_body_classes before_action :set_body_classes

View File

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

View File

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

View File

@ -4,16 +4,19 @@ module Localized
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
before_action :set_locale around_action :set_locale
end end
private private
def set_locale def set_locale
I18n.locale = default_locale locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in?
I18n.locale = current_user.locale if user_signed_in? locale ||= session[:locale] ||= default_locale
rescue I18n::InvalidLocale locale = default_locale unless I18n.available_locales.include?(locale.to_sym)
I18n.locale = default_locale
I18n.with_locale(locale) do
yield
end
end end
def default_locale def default_locale

View File

@ -8,7 +8,7 @@ class EmojisController < ApplicationController
respond_to do |format| respond_to do |format|
format.json do format.json do
expires_in 3.minutes, public: true 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 end
end end

View File

@ -8,6 +8,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :set_pack before_action :set_pack
before_action :set_body_classes before_action :set_body_classes
skip_before_action :require_functional!
include Localized include Localized
def destroy def destroy

View File

@ -3,6 +3,9 @@
class Settings::DeletesController < Settings::BaseController class Settings::DeletesController < Settings::BaseController
prepend_before_action :check_enabled_deletion prepend_before_action :check_enabled_deletion
before_action :require_not_suspended!
skip_before_action :require_functional!
def show def show
@confirmation = Form::DeleteConfirmation.new @confirmation = Form::DeleteConfirmation.new
@ -27,4 +30,8 @@ class Settings::DeletesController < Settings::BaseController
def delete_params def delete_params
params.require(:form_delete_confirmation).permit(:password) params.require(:form_delete_confirmation).permit(:password)
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View File

@ -5,6 +5,8 @@ class Settings::SessionsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_session, only: :destroy before_action :set_session, only: :destroy
skip_before_action :require_functional!
def destroy def destroy
@session.destroy! @session.destroy!
flash[:notice] = I18n.t('sessions.revoke_success') flash[:notice] = I18n.t('sessions.revoke_success')

View File

@ -5,6 +5,8 @@ module Settings
class ConfirmationsController < BaseController class ConfirmationsController < BaseController
before_action :ensure_otp_secret before_action :ensure_otp_secret
skip_before_action :require_functional!
def new def new
prepare_two_factor_form prepare_two_factor_form
end end

View File

@ -3,6 +3,8 @@
module Settings module Settings
module TwoFactorAuthentication module TwoFactorAuthentication
class RecoveryCodesController < BaseController class RecoveryCodesController < BaseController
skip_before_action :require_functional!
def create def create
@recovery_codes = current_user.generate_otp_backup_codes! @recovery_codes = current_user.generate_otp_backup_codes!
current_user.save! current_user.save!

View File

@ -4,6 +4,8 @@ module Settings
class TwoFactorAuthenticationsController < BaseController class TwoFactorAuthenticationsController < BaseController
before_action :verify_otp_required, only: [:create] before_action :verify_otp_required, only: [:create]
skip_before_action :require_functional!
def show def show
@confirmation = Form::TwoFactorConfirmation.new @confirmation = Form::TwoFactorConfirmation.new
end end

View File

@ -34,14 +34,14 @@ class StatusesController < ApplicationController
format.json do format.json do
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? 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 end
end end
def activity def activity
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? 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 end
def embed def embed

View File

@ -22,7 +22,7 @@ export function normalizeAccount(account) {
if (account.fields) { if (account.fields) {
account.fields = account.fields.map(pair => ({ account.fields = account.fields.map(pair => ({
...pair, ...pair,
name_emojified: emojify(escapeTextContentForBrowser(pair.name)), name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap),
value_emojified: emojify(pair.value, emojiMap), value_emojified: emojify(pair.value, emojiMap),
value_plain: unescapeHTML(pair.value), value_plain: unescapeHTML(pair.value),
})); }));

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { autoPlayGif } from 'mastodon/initial_state';
export default class DisplayName extends React.PureComponent { export default class DisplayName extends React.PureComponent {
@ -10,6 +11,47 @@ export default class DisplayName extends React.PureComponent {
localDomain: PropTypes.string, 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 () { render () {
const { others, localDomain } = this.props; const { others, localDomain } = this.props;
@ -39,7 +81,7 @@ export default class DisplayName extends React.PureComponent {
} }
return ( return (
<span className='display-name'> <span className='display-name' ref={this.setRef}>
{displayName} {suffix} {displayName} {suffix}
</span> </span>
); );

View File

@ -7,6 +7,7 @@ import Permalink from './permalink';
import classnames from 'classnames'; import classnames from 'classnames';
import PollContainer from 'mastodon/containers/poll_container'; import PollContainer from 'mastodon/containers/poll_container';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import { autoPlayGif } from 'mastodon/initial_state';
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top) 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 () { componentDidMount () {
this._updateStatusLinks(); this._updateStatusLinks();
this._updateStatusEmojis();
} }
componentDidUpdate () { componentDidUpdate () {
this._updateStatusLinks(); this._updateStatusLinks();
this._updateStatusEmojis();
} }
onMentionClick = (mention, e) => { 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) => { handleMouseDown = (e) => {
this.startXY = [e.clientX, e.clientY]; this.startXY = [e.clientX, e.clientY];
} }

View File

@ -77,7 +77,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
onReblog (status, e) { onReblog (status, e) {
if (e.shiftKey || !boostModal) { if ((e && e.shiftKey) || !boostModal) {
this.onModalReblog(status); this.onModalReblog(status);
} else { } else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));

View File

@ -79,6 +79,47 @@ class Header extends ImmutablePureComponent {
return !location.pathname.match(/\/(followers|following)\/?$/); 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 () { render () {
const { account, intl, domain, identity_proofs } = this.props; 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'); const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
return ( 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__image'>
<div className='account__header__info'> <div className='account__header__info'>
{info} {info}

View File

@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => {
// if you want additional emoji handler, add statements below which set replacement and return true. // if you want additional emoji handler, add statements below which set replacement and return true.
if (shortname in customEmojis) { if (shortname in customEmojis) {
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; 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 true;
} }
return false; return false;

View File

@ -27,6 +27,12 @@ function main() {
} }
}; };
const getEmojiAnimationHandler = (swapTo) => {
return ({ target }) => {
target.src = target.getAttribute(swapTo);
};
};
ready(() => { ready(() => {
const locale = document.documentElement.lang; const locale = document.documentElement.lang;
@ -91,6 +97,9 @@ function main() {
if (parallaxComponents.length > 0 ) { if (parallaxComponents.length > 0 ) {
new Rellax('.parallax', { speed: -1 }); new Rellax('.parallax', { speed: -1 });
} }
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
}); });
} }

View File

@ -204,6 +204,34 @@ $content-width: 840px;
border: 0; border: 0;
} }
} }
}
@media screen and (max-width: $no-columns-breakpoint) {
display: block;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
.sidebar-wrapper,
.content-wrapper {
flex: 0 0 auto;
height: auto;
overflow: initial;
}
.sidebar {
width: 100%;
padding: 0;
height: auto;
}
}
}
hr.spacer {
width: 100%;
border: 0;
margin: 20px 0;
height: 1px;
}
.muted-hint { .muted-hint {
color: $darker-text-color; color: $darker-text-color;
@ -227,26 +255,10 @@ $content-width: 840px;
color: $dark-text-color; color: $dark-text-color;
font-weight: 500; font-weight: 500;
} }
}
@media screen and (max-width: $no-columns-breakpoint) { .warning-hint {
display: block; color: $gold-star;
overflow-y: auto; font-weight: 500;
-webkit-overflow-scrolling: touch;
.sidebar-wrapper,
.content-wrapper {
flex: 0 0 auto;
height: auto;
overflow: initial;
}
.sidebar {
width: 100%;
padding: 0;
height: auto;
}
}
} }
.filters { .filters {

View File

@ -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=text],
input[type=number], input[type=number],
input[type=email], input[type=email],

View File

@ -200,11 +200,7 @@ class Formatter
def encode_custom_emojis(html, emojis, animate = false) def encode_custom_emojis(html, emojis, animate = false)
return html if emojis.empty? return html if emojis.empty?
emoji_map = if animate emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
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
i = -1 i = -1
tag_open_index = nil tag_open_index = nil
@ -220,7 +216,14 @@ class Formatter
emoji = emoji_map[shortcode] emoji = emoji_map[shortcode]
if emoji 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] : '' before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
html = before_html + replacement + html[i + 1..-1] html = before_html + replacement + html[i + 1..-1]
i += replacement.size - (shortcode.size + 2) - 1 i += replacement.size - (shortcode.size + 2) - 1

View File

@ -31,9 +31,6 @@ module AccountAssociations
has_many :media_attachments, dependent: :destroy has_many :media_attachments, dependent: :destroy
has_many :polls, dependent: :destroy has_many :polls, dependent: :destroy
# PuSH subscriptions
has_many :subscriptions, dependent: :destroy
# Report relationships # Report relationships
has_many :reports, dependent: :destroy, inverse_of: :account 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 has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account

View File

@ -43,7 +43,7 @@ module Omniauthable
# Check if the user exists with provided email if the provider gives us a # 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 # 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 # 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 = 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/ user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/

View File

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

View File

@ -161,7 +161,11 @@ class User < ApplicationRecord
end end
def active_for_authentication? def active_for_authentication?
super && approved? true
end
def functional?
confirmed? && approved? && !disabled? && !account.suspended?
end end
def inactive_message def inactive_message

View File

@ -1,7 +0,0 @@
# frozen_string_literal: true
class SubscriptionPolicy < ApplicationPolicy
def index?
admin?
end
end

View File

@ -4,7 +4,7 @@ class REST::WebPushSubscriptionSerializer < ActiveModel::Serializer
attributes :id, :endpoint, :alerts, :server_key attributes :id, :endpoint, :alerts, :server_key
def alerts 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 end
def server_key def server_key

View File

@ -24,7 +24,6 @@ class SuspendAccountService < BaseService
report_notes report_notes
scheduled_statuses scheduled_statuses
status_pins status_pins
subscriptions
).freeze ).freeze
ASSOCIATIONS_ON_DESTROY = %w( ASSOCIATIONS_ON_DESTROY = %w(

View File

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

View File

@ -1,6 +1,8 @@
%h4= t 'sessions.title' %h3= t 'sessions.title'
%p.muted-hint= t 'sessions.explanation' %p.muted-hint= t 'sessions.explanation'
%hr.spacer/
.table-wrapper .table-wrapper
%table.table.inline-table %table.table.inline-table
%thead %thead

View File

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

View File

@ -1,25 +1,28 @@
- content_for :page_title do - 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| = 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 = render 'shared/error_messages', object: resource
- if !use_seamless_external_login? || resource.encrypted_password.present? - if !use_seamless_external_login? || resource.encrypted_password.present?
.fields-group .fields-row
= f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, hint: false .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-group .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 = 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-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 :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 .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 - else
%p.hint= t('users.seamless_external_login') %p.hint= t('users.seamless_external_login')
@ -27,7 +30,7 @@
= render 'sessions' = render 'sessions'
- if open_deletion? - if open_deletion? && !current_account.suspended?
%hr.spacer/ %hr.spacer/
%h4= t('auth.delete_account') %h3= t('auth.delete_account')
%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)

View File

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

View File

@ -17,7 +17,7 @@
= application.name = application.name
- else - else
= link_to application.name, application.website, target: '_blank', rel: 'noopener' = 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= l application.created_at
%td %td
- unless application.superapp? - unless application.superapp?

View File

@ -5,7 +5,5 @@ class Scheduler::SubscriptionsCleanupScheduler
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options unique: :until_executed, retry: 0
def perform def perform; end
Subscription.expired.in_batches.delete_all
end
end end

View File

@ -114,6 +114,9 @@ module Mastodon
Doorkeeper::AuthorizationsController.layout 'modal' Doorkeeper::AuthorizationsController.layout 'modal'
Doorkeeper::AuthorizedApplicationsController.layout 'admin' Doorkeeper::AuthorizedApplicationsController.layout 'admin'
Doorkeeper::Application.send :include, ApplicationExtension 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 end
end end

View File

@ -537,7 +537,6 @@ en:
apply_for_account: Request an invite apply_for_account: Request an invite
change_password: Password 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> 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: 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. 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? didnt_get_confirmation: Didn't receive confirmation instructions?
@ -557,6 +556,14 @@ en:
reset_password: Reset password reset_password: Reset password
security: Security security: Security
set_new_password: Set new password 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? trouble_logging_in: Trouble logging in?
authorize_follow: authorize_follow:
already_following: You are already following this account already_following: You are already following this account

View File

@ -34,7 +34,10 @@ Rails.application.routes.draw do
devise_scope :user do devise_scope :user do
get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite 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 end
devise_for :users, path: 'auth', controllers: { devise_for :users, path: 'auth', controllers: {

View File

@ -21,9 +21,6 @@
user_cleanup_scheduler: user_cleanup_scheduler:
cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *' cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *'
class: Scheduler::UserCleanupScheduler class: Scheduler::UserCleanupScheduler
subscriptions_cleanup_scheduler:
cron: '<%= Random.rand(0..59) %> <%= Random.rand(1..3) %> * * 0'
class: Scheduler::SubscriptionsCleanupScheduler
ip_cleanup_scheduler: ip_cleanup_scheduler:
cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
class: Scheduler::IpCleanupScheduler class: Scheduler::IpCleanupScheduler

View File

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

View File

@ -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 t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
end 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| create_table "tags", force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.datetime "created_at", 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", "statuses", column: "reblog_of_id", on_delete: :cascade
add_foreign_key "statuses_tags", "statuses", 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 "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 "tombstones", "accounts", on_delete: :cascade
add_foreign_key "user_invite_requests", "users", on_delete: :cascade add_foreign_key "user_invite_requests", "users", on_delete: :cascade
add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade

View File

@ -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 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? if Rails.env.development?
admin = Account.where(username: 'admin').first_or_initialize(username: 'admin') admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')

View File

@ -72,7 +72,7 @@
"@babel/preset-env": "^7.4.5", "@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.5.4", "@babel/runtime": "^7.5.4",
"@clusterws/cws": "^0.14.0", "@clusterws/cws": "^0.15.0",
"array-includes": "^3.0.3", "array-includes": "^3.0.3",
"atrament": "^0.2.3", "atrament": "^0.2.3",
"autoprefixer": "^9.6.0", "autoprefixer": "^9.6.0",
@ -106,7 +106,7 @@
"intersection-observer": "^0.7.0", "intersection-observer": "^0.7.0",
"intl": "^1.2.5", "intl": "^1.2.5",
"intl-messageformat": "^2.2.0", "intl-messageformat": "^2.2.0",
"intl-relativeformat": "^6.4.2", "intl-relativeformat": "^6.4.3",
"is-nan": "^1.2.1", "is-nan": "^1.2.1",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"lodash": "^4.17.14", "lodash": "^4.17.14",
@ -169,11 +169,11 @@
"websocket.js": "^0.1.12" "websocket.js": "^0.1.12"
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.2",
"babel-jest": "^24.8.0", "babel-jest": "^24.8.0",
"enzyme": "^3.10.0", "enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-react-16": "^1.14.0",
"eslint": "^5.16.0", "eslint": "^6.1.0",
"eslint-plugin-import": "~2.18.0", "eslint-plugin-import": "~2.18.0",
"eslint-plugin-jsx-a11y": "~6.2.3", "eslint-plugin-jsx-a11y": "~6.2.3",
"eslint-plugin-promise": "~4.2.1", "eslint-plugin-promise": "~4.2.1",

View File

@ -15,7 +15,7 @@ describe Api::BaseController do
end end
end end
describe 'Forgery protection' do describe 'forgery protection' do
before do before do
routes.draw { post 'success' => 'api/base#success' } routes.draw { post 'success' => 'api/base#success' }
end end
@ -27,7 +27,45 @@ describe Api::BaseController do
end end
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 = { ERRORS_WITH_CODES = {
ActiveRecord::RecordInvalid => 422, ActiveRecord::RecordInvalid => 422,
Mastodon::ValidationError => 422, Mastodon::ValidationError => 422,

View File

@ -191,10 +191,10 @@ describe ApplicationController, type: :controller do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end 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))) sign_in(Fabricate(:user, account: Fabricate(:account, suspended: true)))
get 'success' get 'success'
expect(response).to have_http_status(403) expect(response).to redirect_to(edit_user_registration_path)
end end
end end

View File

@ -50,45 +50,4 @@ describe Auth::ConfirmationsController, type: :controller do
end end
end 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 end

View File

@ -46,6 +46,15 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
post :update post :update
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end 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 end
describe 'GET #new' do 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' } } post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
end end
it 'redirects to login page' do it 'redirects to setup' do
subject subject
expect(response).to redirect_to new_user_session_path expect(response).to redirect_to auth_setup_path
end end
it 'creates user' do 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' } } post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
end end
it 'redirects to login page' do it 'redirects to setup' do
subject subject
expect(response).to redirect_to new_user_session_path expect(response).to redirect_to auth_setup_path
end end
it 'creates user' do 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 } } post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
end end
it 'redirects to login page' do it 'redirects to setup' do
subject subject
expect(response).to redirect_to new_user_session_path expect(response).to redirect_to auth_setup_path
end end
it 'creates user' do 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 } } post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
end end
it 'redirects to login page' do it 'redirects to setup' do
subject subject
expect(response).to redirect_to new_user_session_path expect(response).to redirect_to auth_setup_path
end end
it 'creates user' do it 'creates user' do

View File

@ -160,8 +160,8 @@ RSpec.describe Auth::SessionsController, type: :controller do
let(:unconfirmed_user) { user.tap { |u| u.update!(confirmed_at: nil) } } let(:unconfirmed_user) { user.tap { |u| u.update!(confirmed_at: nil) } }
let(:accept_language) { 'fr' } let(:accept_language) { 'fr' }
it 'shows a translated login error' do it 'redirects to home' do
expect(flash[:alert]).to eq(I18n.t('devise.failure.unconfirmed', locale: accept_language)) expect(response).to redirect_to(root_path)
end end
end end

View File

@ -7,16 +7,10 @@ describe ApplicationController, type: :controller do
include Localized include Localized
def success def success
head 200 render plain: I18n.locale, status: 200
end end
end end
around do |example|
current_locale = I18n.locale
example.run
I18n.locale = current_locale
end
before do before do
routes.draw { get 'success' => 'anonymous#success' } routes.draw { get 'success' => 'anonymous#success' }
end end
@ -25,19 +19,19 @@ describe ApplicationController, type: :controller do
it 'sets available and preferred language' do it 'sets available and preferred language' do
request.headers['Accept-Language'] = 'ca-ES, fa' request.headers['Accept-Language'] = 'ca-ES, fa'
get 'success' get 'success'
expect(I18n.locale).to eq :fa expect(response.body).to eq 'fa'
end end
it 'sets available and compatible language if none of available languages are preferred' do it 'sets available and compatible language if none of available languages are preferred' do
request.headers['Accept-Language'] = 'fa-IR' request.headers['Accept-Language'] = 'fa-IR'
get 'success' get 'success'
expect(I18n.locale).to eq :fa expect(response.body).to eq 'fa'
end end
it 'sets default locale if none of available languages are compatible' do it 'sets default locale if none of available languages are compatible' do
request.headers['Accept-Language'] = '' request.headers['Accept-Language'] = ''
get 'success' get 'success'
expect(I18n.locale).to eq :en expect(response.body).to eq 'en'
end end
end end
@ -48,7 +42,7 @@ describe ApplicationController, type: :controller do
sign_in(user) sign_in(user)
get 'success' get 'success'
expect(I18n.locale).to eq :ca expect(response.body).to eq 'ca'
end end
end end

View File

@ -15,6 +15,15 @@ describe Settings::DeletesController do
get :show get :show
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end 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 end
context 'when not signed in' do context 'when not signed in' do
@ -49,6 +58,14 @@ describe Settings::DeletesController do
it 'marks account as suspended' do it 'marks account as suspended' do
expect(user.account.reload).to be_suspended expect(user.account.reload).to be_suspended
end 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 end
context 'with incorrect password' do context 'with incorrect password' do

View File

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

View File

@ -31,12 +31,12 @@ feature "Log in" do
context do context do
given(:confirmed_at) { nil } 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_email", with: email
fill_in "user_password", with: password fill_in "user_password", with: password
click_on I18n.t('auth.login') click_on I18n.t('auth.login')
is_expected.to have_css(".flash-message", text: failure_message("unconfirmed")) is_expected.to have_css("div.admin-wrapper")
end end
end end

View File

@ -261,7 +261,7 @@ RSpec.describe Formatter do
let(:text) { ':coolcat: Beep boop' } let(:text) { ':coolcat: Beep boop' }
it 'converts the shortcode to an image tag' do 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 end
end end
@ -330,7 +330,7 @@ RSpec.describe Formatter do
let(:text) { ':coolcat: Beep boop' } let(:text) { ':coolcat: Beep boop' }
it 'converts the shortcode to an image tag' do 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
end end
@ -338,7 +338,7 @@ RSpec.describe Formatter do
let(:text) { 'Beep :coolcat: boop' } let(:text) { 'Beep :coolcat: boop' }
it 'converts the shortcode to an image tag' do 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
end end
@ -354,7 +354,7 @@ RSpec.describe Formatter do
let(:text) { 'Beep boop :coolcat:' } let(:text) { 'Beep boop :coolcat:' }
it 'converts the shortcode to an image tag' do 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 end
end end
@ -377,7 +377,7 @@ RSpec.describe Formatter do
let(:text) { '<p>:coolcat: Beep boop<br />' } let(:text) { '<p>:coolcat: Beep boop<br />' }
it 'converts the shortcode to an image tag' do 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
end end
@ -385,7 +385,7 @@ RSpec.describe Formatter do
let(:text) { '<p>Beep :coolcat: boop</p>' } let(:text) { '<p>Beep :coolcat: boop</p>' }
it 'converts the shortcode to an image tag' do 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
end end
@ -401,7 +401,7 @@ RSpec.describe Formatter do
let(:text) { '<p>Beep boop<br />:coolcat:</p>' } let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
it 'converts the shortcode to an image tag' do 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 end
end end
@ -500,7 +500,7 @@ RSpec.describe Formatter do
let(:text) { ':coolcat: Beep boop' } let(:text) { ':coolcat: Beep boop' }
it 'converts the shortcode to an image tag' do 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
end end
@ -508,7 +508,7 @@ RSpec.describe Formatter do
let(:text) { 'Beep :coolcat: boop' } let(:text) { 'Beep :coolcat: boop' }
it 'converts the shortcode to an image tag' do 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
end end
@ -524,7 +524,7 @@ RSpec.describe Formatter do
let(:text) { 'Beep boop :coolcat:' } let(:text) { 'Beep boop :coolcat:' }
it 'converts the shortcode to an image tag' do 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 end
end end
@ -551,7 +551,7 @@ RSpec.describe Formatter do
let(:text) { '<p>:coolcat: Beep boop<br />' } let(:text) { '<p>:coolcat: Beep boop<br />' }
it 'converts shortcode to image tag' do 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
end end
@ -559,7 +559,7 @@ RSpec.describe Formatter do
let(:text) { '<p>Beep :coolcat: boop</p>' } let(:text) { '<p>Beep :coolcat: boop</p>' }
it 'converts shortcode to image tag' do 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
end end
@ -575,7 +575,7 @@ RSpec.describe Formatter do
let(:text) { '<p>Beep boop<br />:coolcat:</p>' } let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
it 'converts shortcode to image tag' do 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 end
end end

View File

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

View File

@ -506,7 +506,7 @@ RSpec.describe User, type: :model do
context 'when user is not confirmed' do context 'when user is not confirmed' do
let(:confirmed_at) { nil } let(:confirmed_at) { nil }
it { is_expected.to be false } it { is_expected.to be true }
end end
end end
@ -522,7 +522,7 @@ RSpec.describe User, type: :model do
context 'when user is not confirmed' do context 'when user is not confirmed' do
let(:confirmed_at) { nil } let(:confirmed_at) { nil }
it { is_expected.to be false } it { is_expected.to be true }
end end
end end
end end

View File

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

View File

@ -14,11 +14,8 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
before do before do
allow(Redis.current).to receive_messages(publish: nil) 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) 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.user.update(current_sign_in_at: Time.zone.now)
jeff.follow!(alice) jeff.follow!(alice)
hank.follow!(alice) hank.follow!(alice)

View File

@ -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') } let!(:bill) { Fabricate(:account, username: 'bill', protocol: :activitypub, domain: 'example2.com', inbox_url: 'http://example2.com/inbox') }
before do 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://example.com/inbox').to_return(status: 200)
stub_request(:post, 'http://example2.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) jeff.follow!(alice)
hank.follow!(alice) hank.follow!(alice)

View File

@ -18,7 +18,6 @@ RSpec.describe SuspendAccountService, type: :service do
let!(:favourite) { Fabricate(:favourite, account: account) } let!(:favourite) { Fabricate(:favourite, account: account) }
let!(:active_relationship) { Fabricate(:follow, account: account) } let!(:active_relationship) { Fabricate(:follow, account: account) }
let!(:passive_relationship) { Fabricate(:follow, target_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_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.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.favourites,
account.active_relationships, account.active_relationships,
account.passive_relationships, account.passive_relationships,
account.subscriptions
].map(&:count) ].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 end
it 'sends a delete actor activity to all known inboxes' do 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!(:favourite) { Fabricate(:favourite, account: remote_bob) }
let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) } let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) }
let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) } let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) }
let!(:subscription) { Fabricate(:subscription, account: remote_bob) }
it 'deletes associated records' do it 'deletes associated records' do
is_expected.to change { is_expected.to change {
@ -73,9 +70,8 @@ RSpec.describe SuspendAccountService, type: :service do
remote_bob.favourites, remote_bob.favourites,
remote_bob.active_relationships, remote_bob.active_relationships,
remote_bob.passive_relationships, remote_bob.passive_relationships,
remote_bob.subscriptions
].map(&:count) ].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 end
it 'sends a reject follow to follwer inboxes' do it 'sends a reject follow to follwer inboxes' do

212
yarn.lock
View File

@ -60,7 +60,7 @@
source-map "^0.5.0" source-map "^0.5.0"
trim-right "^1.0.1" trim-right "^1.0.1"
"@babel/generator@^7.2.2", "@babel/generator@^7.4.4": "@babel/generator@^7.4.4":
version "7.4.4" version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ== integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
@ -229,7 +229,7 @@
"@babel/template" "^7.1.0" "@babel/template" "^7.1.0"
"@babel/types" "^7.0.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" version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" 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== integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
@ -273,17 +273,7 @@
esutils "^2.0.2" esutils "^2.0.2"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/parser@^7.0.0": "@babel/parser@^7.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.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":
version "7.4.5" version "7.4.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872"
integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew== integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==
@ -793,22 +783,7 @@
"@babel/parser" "^7.4.4" "@babel/parser" "^7.4.4"
"@babel/types" "^7.4.4" "@babel/types" "^7.4.4"
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.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.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":
version "7.4.5" version "7.4.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216"
integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A== integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==
@ -823,22 +798,7 @@
globals "^11.1.0" globals "^11.1.0"
lodash "^4.17.11" lodash "^4.17.11"
"@babel/traverse@^7.3.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.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":
version "7.4.4" version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ== integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
@ -847,28 +807,10 @@
lodash "^4.17.11" lodash "^4.17.11"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@babel/types@^7.0.0-beta.49": "@clusterws/cws@^0.15.0":
version "7.2.2" version "0.15.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e" resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.15.0.tgz#1d585927252d1cd92e676c952fa6d69df14a0d07"
integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg== integrity sha512-41QpCngw86n41hIdU5Nx2QJmmxZuA9FPtDkjONrYpk27L7HjL1kj6J5oWEjbr14yXLfigZit3VY+oACDCGbiHw==
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==
"@cnakazawa/watch@^1.0.3": "@cnakazawa/watch@^1.0.3":
version "1.0.3" version "1.0.3"
@ -1423,10 +1365,10 @@ ajv@^4.7.0:
co "^4.6.0" co "^4.6.0"
json-stable-stringify "^1.0.1" json-stable-stringify "^1.0.1"
ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1: ajv@^6.1.0, ajv@^6.10.0, ajv@^6.5.5, ajv@^6.9.1:
version "6.10.0" version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
dependencies: dependencies:
fast-deep-equal "^2.0.1" fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0" fast-json-stable-stringify "^2.0.0"
@ -1728,10 +1670,10 @@ axobject-query@^2.0.2:
dependencies: dependencies:
ast-types-flow "0.0.7" ast-types-flow "0.0.7"
babel-eslint@^10.0.1: babel-eslint@^10.0.2:
version "10.0.1" version "10.0.2"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ== integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.0.0" "@babel/parser" "^7.0.0"
@ -3709,7 +3651,7 @@ eslint-scope@3.7.1:
esrecurse "^4.1.0" esrecurse "^4.1.0"
estraverse "^4.1.1" estraverse "^4.1.1"
eslint-scope@^4.0.0, eslint-scope@^4.0.3: eslint-scope@^4.0.0:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== 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" esrecurse "^4.1.0"
estraverse "^4.1.1" 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: eslint-utils@^1.3.1:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" 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" text-table "~0.2.0"
user-home "^2.0.0" user-home "^2.0.0"
eslint@^5.16.0: eslint@^6.1.0:
version "5.16.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.1.0.tgz#06438a4a278b1d84fb107d24eaaa35471986e646"
integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== integrity sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
ajv "^6.9.1" ajv "^6.10.0"
chalk "^2.1.0" chalk "^2.1.0"
cross-spawn "^6.0.5" cross-spawn "^6.0.5"
debug "^4.0.1" debug "^4.0.1"
doctrine "^3.0.0" doctrine "^3.0.0"
eslint-scope "^4.0.3" eslint-scope "^5.0.0"
eslint-utils "^1.3.1" eslint-utils "^1.3.1"
eslint-visitor-keys "^1.0.0" eslint-visitor-keys "^1.0.0"
espree "^5.0.1" espree "^6.0.0"
esquery "^1.0.1" esquery "^1.0.1"
esutils "^2.0.2" esutils "^2.0.2"
file-entry-cache "^5.0.1" file-entry-cache "^5.0.1"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
glob "^7.1.2" glob-parent "^5.0.0"
globals "^11.7.0" globals "^11.7.0"
ignore "^4.0.6" ignore "^4.0.6"
import-fresh "^3.0.0" import-fresh "^3.0.0"
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
inquirer "^6.2.2" inquirer "^6.4.1"
js-yaml "^3.13.0" is-glob "^4.0.0"
js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1" json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.3.0" levn "^0.3.0"
lodash "^4.17.11" lodash "^4.17.14"
minimatch "^3.0.4" minimatch "^3.0.4"
mkdirp "^0.5.1" mkdirp "^0.5.1"
natural-compare "^1.4.0" natural-compare "^1.4.0"
optionator "^0.8.2" optionator "^0.8.2"
path-is-inside "^1.0.2"
progress "^2.0.0" progress "^2.0.0"
regexpp "^2.0.1" regexpp "^2.0.1"
semver "^5.5.1" semver "^6.1.2"
strip-ansi "^4.0.0" strip-ansi "^5.2.0"
strip-json-comments "^2.0.1" strip-json-comments "^3.0.1"
table "^5.2.3" table "^5.2.3"
text-table "^0.2.0" text-table "^0.2.0"
v8-compile-cache "^2.0.3"
espree@^3.1.6: espree@^3.1.6:
version "3.5.4" version "3.5.4"
@ -3816,10 +3767,10 @@ espree@^3.1.6:
acorn "^5.5.0" acorn "^5.5.0"
acorn-jsx "^3.0.0" acorn-jsx "^3.0.0"
espree@^5.0.1: espree@^6.0.0:
version "5.0.1" version "6.0.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" resolved "https://registry.yarnpkg.com/espree/-/espree-6.0.0.tgz#716fc1f5a245ef5b9a7fdb1d7b0d3f02322e75f6"
integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== integrity sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==
dependencies: dependencies:
acorn "^6.0.7" acorn "^6.0.7"
acorn-jsx "^5.0.0" acorn-jsx "^5.0.0"
@ -4481,6 +4432,13 @@ glob-parent@^3.1.0:
is-glob "^3.1.0" is-glob "^3.1.0"
path-dirname "^1.0.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: 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" version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" 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" strip-ansi "^3.0.0"
through "^2.3.6" through "^2.3.6"
inquirer@^6.2.2: inquirer@^6.4.1:
version "6.4.1" version "6.5.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.4.1.tgz#7bd9e5ab0567cd23b41b0180b68e0cfa82fc3c0b" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
integrity sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw== integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==
dependencies: dependencies:
ansi-escapes "^3.2.0" ansi-escapes "^3.2.0"
chalk "^2.4.2" chalk "^2.4.2"
@ -5038,7 +4996,7 @@ inquirer@^6.2.2:
cli-width "^2.0.0" cli-width "^2.0.0"
external-editor "^3.0.3" external-editor "^3.0.3"
figures "^2.0.0" figures "^2.0.0"
lodash "^4.17.11" lodash "^4.17.12"
mute-stream "0.0.7" mute-stream "0.0.7"
run-async "^2.2.0" run-async "^2.2.0"
rxjs "^6.4.0" rxjs "^6.4.0"
@ -5093,10 +5051,10 @@ intl-relativeformat@^2.1.0:
dependencies: dependencies:
intl-messageformat "^2.0.0" intl-messageformat "^2.0.0"
intl-relativeformat@^6.4.2: intl-relativeformat@^6.4.3:
version "6.4.2" version "6.4.3"
resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.2.tgz#431f9818449f5b48c209610ff1428d0c663c667f" resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.3.tgz#cb5559e1e257cc2e763583502012a354bb777efe"
integrity sha512-yaOimRUQEn1wOfVGk43H+EVCrxQ5WFEvtYBx4Ffa6QpEHIi6UOuvshx6RltuqIF5UM8xdF4SkzFHXXOnYXlgBA== integrity sha512-VxZXZfhuX/zBVfxzE/J6kPUpsyWKYjqtZ3jVGZwr6wzK5BOLVpe1vSlwCQX56w5UjlpL63fS8Nxq0kgTyf1gJA==
intl@^1.2.5: intl@^1.2.5:
version "1.2.5" version "1.2.5"
@ -5295,10 +5253,10 @@ is-glob@^3.1.0:
dependencies: dependencies:
is-extglob "^2.1.0" is-extglob "^2.1.0"
is-glob@^4.0.0: is-glob@^4.0.0, is-glob@^4.0.1:
version "4.0.0" version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies: dependencies:
is-extglob "^2.1.1" 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" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 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" version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== 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" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= 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: 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.14" version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
loglevel@^1.6.3: loglevel@^1.6.3:
version "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" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
semver@^6.0.0: semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2:
version "6.1.3" version "6.2.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.3.tgz#ef997a1a024f67dd48a7f155df88bb7b5c6c3fc7" resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db"
integrity sha512-aymF+56WJJMyXQHcd4hlK4N75rwj5RQpfW8ePlQnJsTYOBLlLbcIErR/G1s9SkIvKBqOudR3KAx4wEqP+F1hNQ== integrity sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==
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==
send@0.17.1: send@0.17.1:
version "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" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: strip-json-comments@^3.0.1:
version "2.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
strip-json-comments@~1.0.1: strip-json-comments@~1.0.1:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= 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: stylehacks@^4.0.0:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.1.tgz#3186595d047ab0df813d213e51c8b94e0b9010f2" 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" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== 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" version "2.0.3"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==