diff --git a/Gemfile b/Gemfile index e059cea23..f0054dcc5 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ ruby '>= 2.5.0', '< 3.1.0' gem 'pkg-config', '~> 1.4' gem 'puma', '~> 5.3' -gem 'rails', '~> 6.1.3' +gem 'rails', '~> 6.1.4' gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 1.1' gem 'rack', '~> 2.2.3' @@ -24,7 +24,7 @@ gem 'paperclip', '~> 6.0' gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' -gem 'addressable', '~> 2.7' +gem 'addressable', '~> 2.8' gem 'bootsnap', '~> 1.6.0', require: false gem 'browser' gem 'charlock_holmes', '~> 0.7.7' diff --git a/Gemfile.lock b/Gemfile.lock index a96cfb980..04f8be599 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,40 +1,40 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.3.2) - actionpack (= 6.1.3.2) - activesupport (= 6.1.3.2) + actioncable (6.1.4) + actionpack (= 6.1.4) + activesupport (= 6.1.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.3.2) - actionpack (= 6.1.3.2) - activejob (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionmailbox (6.1.4) + actionpack (= 6.1.4) + activejob (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) mail (>= 2.7.1) - actionmailer (6.1.3.2) - actionpack (= 6.1.3.2) - actionview (= 6.1.3.2) - activejob (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionmailer (6.1.4) + actionpack (= 6.1.4) + actionview (= 6.1.4) + activejob (= 6.1.4) + activesupport (= 6.1.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.3.2) - actionview (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionpack (6.1.4) + actionview (= 6.1.4) + activesupport (= 6.1.4) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.3.2) - actionpack (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + actiontext (6.1.4) + actionpack (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) nokogiri (>= 1.8.5) - actionview (6.1.3.2) - activesupport (= 6.1.3.2) + actionview (6.1.4) + activesupport (= 6.1.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -45,28 +45,28 @@ GEM case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.8) - activejob (6.1.3.2) - activesupport (= 6.1.3.2) + activejob (6.1.4) + activesupport (= 6.1.4) globalid (>= 0.3.6) - activemodel (6.1.3.2) - activesupport (= 6.1.3.2) - activerecord (6.1.3.2) - activemodel (= 6.1.3.2) - activesupport (= 6.1.3.2) - activestorage (6.1.3.2) - actionpack (= 6.1.3.2) - activejob (= 6.1.3.2) - activerecord (= 6.1.3.2) - activesupport (= 6.1.3.2) + activemodel (6.1.4) + activesupport (= 6.1.4) + activerecord (6.1.4) + activemodel (= 6.1.4) + activesupport (= 6.1.4) + activestorage (6.1.4) + actionpack (= 6.1.4) + activejob (= 6.1.4) + activerecord (= 6.1.4) + activesupport (= 6.1.4) marcel (~> 1.0.0) - mini_mime (~> 1.0.2) - activesupport (6.1.3.2) + mini_mime (>= 1.1.0) + activesupport (6.1.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) airbrussh (1.4.0) sshkit (>= 1.6.1, != 1.7.0) @@ -353,7 +353,7 @@ GEM mimemagic (0.3.10) nokogiri (~> 1) rake - mini_mime (1.0.3) + mini_mime (1.1.0) mini_portile2 (2.5.3) minitest (5.14.4) msgpack (1.4.2) @@ -374,7 +374,7 @@ GEM concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) - oj (3.11.7) + oj (3.11.8) omniauth (1.9.1) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) @@ -443,20 +443,20 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.3.2) - actioncable (= 6.1.3.2) - actionmailbox (= 6.1.3.2) - actionmailer (= 6.1.3.2) - actionpack (= 6.1.3.2) - actiontext (= 6.1.3.2) - actionview (= 6.1.3.2) - activejob (= 6.1.3.2) - activemodel (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + rails (6.1.4) + actioncable (= 6.1.4) + actionmailbox (= 6.1.4) + actionmailer (= 6.1.4) + actionpack (= 6.1.4) + actiontext (= 6.1.4) + actionview (= 6.1.4) + activejob (= 6.1.4) + activemodel (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) bundler (>= 1.15.0) - railties (= 6.1.3.2) + railties (= 6.1.4) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -472,11 +472,11 @@ GEM railties (>= 6.0.0, < 7) rails-settings-cached (0.6.6) rails (>= 4.2.0) - railties (6.1.3.2) - actionpack (= 6.1.3.2) - activesupport (= 6.1.3.2) + railties (6.1.4) + actionpack (= 6.1.4) + activesupport (= 6.1.4) method_source - rake (>= 0.8.7) + rake (>= 0.13) thor (~> 1.0) rainbow (3.0.0) rake (13.0.3) @@ -525,7 +525,7 @@ GEM rspec-support (3.10.2) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.18.1) + rubocop (1.18.2) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -536,7 +536,7 @@ GEM unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.7.0) parser (>= 3.0.1.1) - rubocop-rails (2.11.1) + rubocop-rails (2.11.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) @@ -570,7 +570,7 @@ GEM sidekiq (>= 3) thwait tilt (>= 1.4.0) - sidekiq-unique-jobs (7.1.1) + sidekiq-unique-jobs (7.1.2) brpoplpush-redis_script (> 0.1.1, <= 2.0.0) concurrent-ruby (~> 1.0, >= 1.0.5) sidekiq (>= 5.0, < 7.0) @@ -659,7 +659,7 @@ GEM webpush (0.3.8) hkdf (~> 0.2) jwt (~> 2.0) - websocket-driver (0.7.3) + websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) wisper (2.0.1) @@ -674,7 +674,7 @@ PLATFORMS DEPENDENCIES active_model_serializers (~> 0.10) active_record_query_trace (~> 1.8) - addressable (~> 2.7) + addressable (~> 2.8) annotate (~> 3.1) aws-sdk-s3 (~> 1.96) better_errors (~> 2.9) @@ -758,7 +758,7 @@ DEPENDENCIES rack (~> 2.2.3) rack-attack (~> 6.5) rack-cors (~> 1.1) - rails (~> 6.1.3) + rails (~> 6.1.4) rails-controller-testing (~> 1.0) rails-i18n (~> 6.0) rails-settings-cached (~> 0.6) diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 4a52560ac..b2aab56a5 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -11,7 +11,11 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController before_action :set_cache_headers def show - expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?)) + if page_requested? + expires_in(1.minute, public: public_fetch_mode? && signed_request_account.nil?) + else + expires_in(3.minutes, public: public_fetch_mode?) + end render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end @@ -76,4 +80,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController def set_account @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative end + + def set_cache_headers + response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested? + end end diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb index db8f61d64..7962b7a58 100644 --- a/app/controllers/admin/resets_controller.rb +++ b/app/controllers/admin/resets_controller.rb @@ -6,9 +6,9 @@ module Admin def create authorize @user, :reset_password? - @user.send_reset_password_instructions + @user.reset_password! log_action :reset_password, @user - redirect_to admin_accounts_path + redirect_to admin_account_path(@user.account_id) end end end diff --git a/app/controllers/admin/sign_in_token_authentications_controller.rb b/app/controllers/admin/sign_in_token_authentications_controller.rb new file mode 100644 index 000000000..e620ab292 --- /dev/null +++ b/app/controllers/admin/sign_in_token_authentications_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Admin + class SignInTokenAuthenticationsController < BaseController + before_action :set_target_user + + def create + authorize @user, :enable_sign_in_token_auth? + @user.update(skip_sign_in_token: false) + log_action :enable_sign_in_token_auth, @user + redirect_to admin_account_path(@user.account_id) + end + + def destroy + authorize @user, :disable_sign_in_token_auth? + @user.update(skip_sign_in_token: true) + log_action :disable_sign_in_token_auth, @user + redirect_to admin_account_path(@user.account_id) + end + + private + + def set_target_user + @user = User.find(params[:user_id]) + end + end +end diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb index 0652c3a7a..f7fb7eb8f 100644 --- a/app/controllers/admin/two_factor_authentications_controller.rb +++ b/app/controllers/admin/two_factor_authentications_controller.rb @@ -9,7 +9,7 @@ module Admin @user.disable_two_factor! log_action :disable_2fa, @user UserMailer.two_factor_disabled(@user).deliver_later! - redirect_to admin_accounts_path + redirect_to admin_account_path(@user.account_id) end private diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 0227f722a..2b296ea3b 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -4,7 +4,6 @@ module WellKnown class WebfingerController < ActionController::Base include RoutingHelper - before_action { response.headers['Vary'] = 'Accept' } before_action :set_account before_action :check_account_suspension @@ -39,10 +38,12 @@ module WellKnown end def bad_request + expires_in(3.minutes, public: true) head 400 end def not_found + expires_in(3.minutes, public: true) head 404 end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index e977db2c6..bb2374c0e 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -84,19 +84,19 @@ module AccountsHelper def account_description(account) prepend_stats = [ [ - number_to_human(account.statuses_count, strip_insignificant_zeros: true), + number_to_human(account.statuses_count, precision: 3, strip_insignificant_zeros: true), I18n.t('accounts.posts', count: account.statuses_count), ].join(' '), [ - number_to_human(account.following_count, strip_insignificant_zeros: true), + number_to_human(account.following_count, precision: 3, strip_insignificant_zeros: true), I18n.t('accounts.following', count: account.following_count), ].join(' '), ] unless hide_followers_count?(account) prepend_stats << [ - number_to_human(account.followers_count, strip_insignificant_zeros: true), + number_to_human(account.followers_count, precision: 3, strip_insignificant_zeros: true), I18n.t('accounts.followers', count: account.followers_count), ].join(' ') end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5a9496bd4..cc622478d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -14,6 +14,17 @@ module ApplicationHelper ku ).freeze + def friendly_number_to_human(number, **options) + # By default, the number of precision digits used by number_to_human + # is looked up from the locales definition, and rails-i18n comes with + # values that don't seem to make much sense for many languages, so + # override these values with a default of 3 digits of precision. + options[:precision] = 3 + options[:strip_insignificant_zeros] = true + + number_to_human(number, **options) + end + def active_nav_class(*paths) paths.any? { |path| current_page?(path) } ? 'active' : '' end diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index e906a7261..e9d30544f 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -1095,6 +1095,7 @@ a.status-card.compact:hover { &__account { display: flex; text-decoration: none; + overflow: hidden; } .account__avatar { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 33f1e2989..0c7916b50 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7291,6 +7291,7 @@ noscript { &__account { display: flex; text-decoration: none; + overflow: hidden; } .account__avatar { diff --git a/app/models/account_stat.rb b/app/models/account_stat.rb index 44da4f0d0..e702fa4a4 100644 --- a/app/models/account_stat.rb +++ b/app/models/account_stat.rb @@ -15,6 +15,7 @@ class AccountStat < ApplicationRecord self.locking_column = nil + self.ignored_columns = %w(lock_version) belongs_to :account, inverse_of: :account_stat diff --git a/app/models/user.rb b/app/models/user.rb index 5c5e926e6..a1a278004 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -42,6 +42,7 @@ # sign_in_token_sent_at :datetime # webauthn_id :string # sign_up_ip :inet +# skip_sign_in_token :boolean # class User < ApplicationRecord @@ -200,7 +201,7 @@ class User < ApplicationRecord end def suspicious_sign_in?(ip) - !otp_required_for_login? && current_sign_in_at.present? && current_sign_in_at < 2.weeks.ago && !recent_ip?(ip) + !otp_required_for_login? && !skip_sign_in_token? && current_sign_in_at.present? && !recent_ip?(ip) end def functional? @@ -329,12 +330,32 @@ class User < ApplicationRecord super end - def reset_password!(new_password, new_password_confirmation) + def reset_password(new_password, new_password_confirmation) return false if encrypted_password.blank? super end + def reset_password! + # First, change password to something random, invalidate the remember-me token, + # and deactivate all sessions + transaction do + update(remember_token: nil, remember_created_at: nil, password: SecureRandom.hex) + session_activations.destroy_all + end + + # Then, remove all authorized applications and connected push subscriptions + Doorkeeper::AccessGrant.by_resource_owner(self).in_batches.update_all(revoked_at: Time.now.utc) + + Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch| + batch.update_all(revoked_at: Time.now.utc) + Web::PushSubscription.where(access_token_id: batch).delete_all + end + + # Finally, send a reset password prompt to the user + send_reset_password_instructions + end + def show_all_media? setting_display_media == 'show_all' end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index d832bff75..6695a0ddf 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -13,6 +13,14 @@ class UserPolicy < ApplicationPolicy admin? && !record.staff? end + def disable_sign_in_token_auth? + staff? + end + + def enable_sign_in_token_auth? + staff? + end + def confirm? staff? && !record.confirmed? end diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 1cf194522..a4a79c4e7 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -16,11 +16,11 @@ .row__information-board .information-board__section %span= t 'about.user_count_before' - %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true + %strong= friendly_number_to_human @instance_presenter.user_count %span= t 'about.user_count_after', count: @instance_presenter.user_count .information-board__section %span= t 'about.status_count_before' - %strong= number_to_human @instance_presenter.status_count, strip_insignificant_zeros: true + %strong= friendly_number_to_human @instance_presenter.status_count %span= t 'about.status_count_after', count: @instance_presenter.status_count .row__mascot .landing-page__mascot diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index 565c4ed59..6ae9e6ae0 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -70,10 +70,10 @@ .hero-widget__counters__wrapper .hero-widget__counter - %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true + %strong= friendly_number_to_human @instance_presenter.user_count %span= t 'about.user_count_after', count: @instance_presenter.user_count .hero-widget__counter - %strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true + %strong= friendly_number_to_human @instance_presenter.active_user_count %span = t 'about.active_count_after' %abbr{ title: t('about.active_footnote') } * diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 76dec18b1..d583edbd2 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -15,17 +15,17 @@ .details-counters .counter{ class: active_nav_class(short_account_url(account), short_account_with_replies_url(account), short_account_media_url(account)) } = link_to short_account_url(account), class: 'u-url u-uid', title: number_with_delimiter(account.statuses_count) do - %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true + %span.counter-number= friendly_number_to_human account.statuses_count %span.counter-label= t('accounts.posts', count: account.statuses_count) .counter{ class: active_nav_class(account_following_index_url(account)) } = link_to account_following_index_url(account), title: number_with_delimiter(account.following_count) do - %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true + %span.counter-number= friendly_number_to_human account.following_count %span.counter-label= t('accounts.following', count: account.following_count) .counter{ class: active_nav_class(account_followers_url(account)) } = link_to account_followers_url(account), title: hide_followers_count?(account) ? nil : number_with_delimiter(account.followers_count) do - %span.counter-number= hide_followers_count?(account) ? '-' : (number_to_human account.followers_count, strip_insignificant_zeros: true) + %span.counter-number= hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count) %span.counter-label= t('accounts.followers', count: account.followers_count) .spacer .public-account-header__tabs__tabs__buttons @@ -36,8 +36,8 @@ .public-account-header__extra__links = link_to account_following_index_url(account) do - %strong= number_to_human account.following_count, strip_insignificant_zeros: true + %strong= friendly_number_to_human account.following_count = t('accounts.following', count: account.following_count) = link_to account_followers_url(account) do - %strong= hide_followers_count?(account) ? '-' : (number_to_human account.followers_count, strip_insignificant_zeros: true) + %strong= hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count) = t('accounts.followers', count: account.followers_count) diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 1a81b96f6..72e9c6611 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -81,6 +81,6 @@ = t('accounts.nothing_here') - else %time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at - .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true + .trends__item__current= friendly_number_to_human featured_tag.statuses_count = render 'application/sidebar' diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 27e1f80a7..66eb49342 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -129,6 +129,27 @@ - else = t('admin.accounts.confirming') %td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user) + %tr + %th{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }= t('admin.accounts.security') + %td{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 } + - if @account.user&.two_factor_enabled? + = t 'admin.accounts.security_measures.password_and_2fa' + - elsif @account.user&.skip_sign_in_token? + = t 'admin.accounts.security_measures.only_password' + - else + = t 'admin.accounts.security_measures.password_and_sign_in_token' + %td + - if @account.user&.two_factor_enabled? + = table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete if can?(:disable_2fa, @account.user) + - elsif @account.user&.skip_sign_in_token? + = table_link_to 'lock', t('admin.accounts.enable_sign_in_token_auth'), admin_user_sign_in_token_authentication_path(@account.user.id), method: :post if can?(:enable_sign_in_token_auth, @account.user) + - else + = table_link_to 'unlock', t('admin.accounts.disable_sign_in_token_auth'), admin_user_sign_in_token_authentication_path(@account.user.id), method: :delete if can?(:disable_sign_in_token_auth, @account.user) + + - if can?(:reset_password, @account.user) + %tr + %td + = table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') } %tr %th= t('simple_form.labels.defaults.locale') @@ -221,9 +242,6 @@ %div - if @account.local? - = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) - - if @account.user&.otp_required_for_login? - = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) - if !@account.memorial? && @account.user_approved? = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account) - else diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index e25b80846..bd36580e6 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -13,42 +13,42 @@ %div = link_to admin_accounts_url(local: 1, recent: 1) do .dashboard__counters__num{ title: number_with_delimiter(@users_count, strip_insignificant_zeros: true) } - = number_to_human @users_count, strip_insignificant_zeros: true + = friendly_number_to_human @users_count .dashboard__counters__label= t 'admin.dashboard.total_users' %div %div .dashboard__counters__num{ title: number_with_delimiter(@registrations_week, strip_insignificant_zeros: true) } - = number_to_human @registrations_week, strip_insignificant_zeros: true + = friendly_number_to_human @registrations_week .dashboard__counters__label= t 'admin.dashboard.week_users_new' %div %div .dashboard__counters__num{ title: number_with_delimiter(@logins_week, strip_insignificant_zeros: true) } - = number_to_human @logins_week, strip_insignificant_zeros: true + = friendly_number_to_human @logins_week .dashboard__counters__label= t 'admin.dashboard.week_users_active' %div = link_to admin_pending_accounts_path do .dashboard__counters__num{ title: number_with_delimiter(@pending_users_count, strip_insignificant_zeros: true) } - = number_to_human @pending_users_count, strip_insignificant_zeros: true + = friendly_number_to_human @pending_users_count .dashboard__counters__label= t 'admin.dashboard.pending_users' %div = link_to admin_reports_url do .dashboard__counters__num{ title: number_with_delimiter(@reports_count, strip_insignificant_zeros: true) } - = number_to_human @reports_count, strip_insignificant_zeros: true + = friendly_number_to_human @reports_count .dashboard__counters__label= t 'admin.dashboard.open_reports' %div = link_to admin_tags_path(pending_review: '1') do .dashboard__counters__num{ title: number_with_delimiter(@pending_tags_count, strip_insignificant_zeros: true) } - = number_to_human @pending_tags_count, strip_insignificant_zeros: true + = friendly_number_to_human @pending_tags_count .dashboard__counters__label= t 'admin.dashboard.pending_tags' %div %div .dashboard__counters__num{ title: number_with_delimiter(@interactions_week, strip_insignificant_zeros: true) } - = number_to_human @interactions_week, strip_insignificant_zeros: true + = friendly_number_to_human @interactions_week .dashboard__counters__label= t 'admin.dashboard.week_interactions' %div = link_to sidekiq_url do .dashboard__counters__num{ title: number_with_delimiter(@queue_backlog, strip_insignificant_zeros: true) } - = number_to_human @queue_backlog, strip_insignificant_zeros: true + = friendly_number_to_human @queue_backlog .dashboard__counters__label= t 'admin.dashboard.backlog' .dashboard__widgets diff --git a/app/views/admin/follow_recommendations/_account.html.haml b/app/views/admin/follow_recommendations/_account.html.haml index af5a4aaf7..00196dd01 100644 --- a/app/views/admin/follow_recommendations/_account.html.haml +++ b/app/views/admin/follow_recommendations/_account.html.haml @@ -7,10 +7,10 @@ %tr %td= account_link_to account %td.accounts-table__count.optional - = number_to_human account.statuses_count, strip_insignificant_zeros: true + = friendly_number_to_human account.statuses_count %small= t('accounts.posts', count: account.statuses_count).downcase %td.accounts-table__count.optional - = number_to_human account.followers_count, strip_insignificant_zeros: true + = friendly_number_to_human account.followers_count %small= t('accounts.followers', count: account.followers_count).downcase %td.accounts-table__count - if account.last_status_at.present? diff --git a/app/views/admin/instances/_instance.html.haml b/app/views/admin/instances/_instance.html.haml index 990cf9ec8..dc81007ac 100644 --- a/app/views/admin/instances/_instance.html.haml +++ b/app/views/admin/instances/_instance.html.haml @@ -30,4 +30,4 @@ = ' / ' %span.negative-hint = t('admin.instances.delivery.unavailable_message') - .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true + .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= friendly_number_to_human instance.accounts_count diff --git a/app/views/admin/tags/_tag.html.haml b/app/views/admin/tags/_tag.html.haml index adf4ca7b2..ac0c72816 100644 --- a/app/views/admin/tags/_tag.html.haml +++ b/app/views/admin/tags/_tag.html.haml @@ -16,4 +16,4 @@ = fa_icon 'fire fw' = t('admin.tags.trending_right_now') - .trends__item__current= number_to_human tag.history.first[:uses], strip_insignificant_zeros: true + .trends__item__current= friendly_number_to_human tag.history.first[:uses] diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index febfb7d17..d5509f946 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -39,10 +39,10 @@ .directory__card__extra .accounts-table__count - = number_to_human account.statuses_count, strip_insignificant_zeros: true + = friendly_number_to_human account.statuses_count %small= t('accounts.posts', count: account.statuses_count).downcase .accounts-table__count - = hide_followers_count?(account) ? '-' : (number_to_human account.followers_count, strip_insignificant_zeros: true) + = hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count) %small= t('accounts.followers', count: account.followers_count).downcase .accounts-table__count - if account.last_status_at.present? diff --git a/app/views/relationships/_account.html.haml b/app/views/relationships/_account.html.haml index f521aff22..0fa3cffb5 100644 --- a/app/views/relationships/_account.html.haml +++ b/app/views/relationships/_account.html.haml @@ -9,10 +9,10 @@ = interrelationships_icon(@relationships, account.id) %td= account_link_to account %td.accounts-table__count.optional - = number_to_human account.statuses_count, strip_insignificant_zeros: true + = friendly_number_to_human account.statuses_count %small= t('accounts.posts', count: account.statuses_count).downcase %td.accounts-table__count.optional - = number_to_human account.followers_count, strip_insignificant_zeros: true + = friendly_number_to_human account.followers_count %small= t('accounts.followers', count: account.followers_count).downcase %td.accounts-table__count - if account.last_status_at.present? diff --git a/app/views/settings/featured_tags/index.html.haml b/app/views/settings/featured_tags/index.html.haml index 297379893..65de7f8f3 100644 --- a/app/views/settings/featured_tags/index.html.haml +++ b/app/views/settings/featured_tags/index.html.haml @@ -28,4 +28,4 @@ - else %time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at = table_link_to 'trash', t('filters.index.delete'), settings_featured_tag_path(featured_tag), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } - .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true + .trends__item__current= friendly_number_to_human featured_tag.statuses_count diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index daf164949..6b3b81306 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -55,18 +55,18 @@ = fa_icon('reply') - else = fa_icon('reply-all') - %span.detailed-status__reblogs>= number_to_human status.replies_count, strip_insignificant_zeros: true + %span.detailed-status__reblogs>= friendly_number_to_human status.replies_count = " " · - if status.public_visibility? || status.unlisted_visibility? = link_to remote_interaction_path(status, type: :reblog), class: 'modal-button detailed-status__link' do = fa_icon('retweet') - %span.detailed-status__reblogs>= number_to_human status.reblogs_count, strip_insignificant_zeros: true + %span.detailed-status__reblogs>= friendly_number_to_human status.reblogs_count = " " · = link_to remote_interaction_path(status, type: :favourite), class: 'modal-button detailed-status__link' do = fa_icon('star') - %span.detailed-status__favorites>= number_to_human status.favourites_count, strip_insignificant_zeros: true + %span.detailed-status__favorites>= friendly_number_to_human status.favourites_count = " " - if user_signed_in? diff --git a/config/locales/en.yml b/config/locales/en.yml index cdb2e3df7..51764a0e1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -44,7 +44,7 @@ en: rejecting_media: 'Media files from these servers will not be processed or stored, and no thumbnails will be displayed, requiring manual click-through to the original file:' rejecting_media_title: Filtered media silenced: 'Posts from these servers will be hidden in public timelines and conversations, and no notifications will be generated from their users interactions, unless you are following them:' - silenced_title: Silenced servers + silenced_title: Limited servers suspended: 'No data from these servers will be processed, stored or exchanged, making any interaction or communication with users from these servers impossible:' suspended_title: Suspended servers unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server. @@ -119,6 +119,7 @@ en: demote: Demote destroyed_msg: "%{username}'s data is now queued to be deleted imminently" disable: Freeze + disable_sign_in_token_auth: Disable e-mail token authentication disable_two_factor_authentication: Disable 2FA disabled: Frozen display_name: Display name @@ -127,6 +128,7 @@ en: email: Email email_status: Email status enable: Unfreeze + enable_sign_in_token_auth: Enable e-mail token authentication enabled: Enabled enabled_msg: Successfully unfroze %{username}'s account followers: Followers @@ -151,7 +153,7 @@ en: active: Active all: All pending: Pending - silenced: Silenced + silenced: Limited suspended: Suspended title: Moderation moderation_notes: Moderation notes @@ -191,8 +193,12 @@ en: search: Search search_same_email_domain: Other users with the same e-mail domain search_same_ip: Other users with the same IP - sensitive: Sensitive - sensitized: marked as sensitive + security_measures: + only_password: Only password + password_and_2fa: Password and 2FA + password_and_sign_in_token: Password and e-mail token + sensitive: Force-sensitive + sensitized: Marked as sensitive shared_inbox_url: Shared inbox URL show: created_reports: Made reports @@ -207,10 +213,10 @@ en: time_in_queue: Waiting in queue %{time} title: Accounts unconfirmed_email: Unconfirmed email - undo_sensitized: Undo sensitive - undo_silenced: Undo silence + undo_sensitized: Undo force-sensitive + undo_silenced: Undo limit undo_suspension: Undo suspension - unsilenced_msg: Successfully unlimited %{username}'s account + unsilenced_msg: Successfully undid limit of %{username}'s account unsubscribe: Unsubscribe unsuspended_msg: Successfully unsuspended %{username}'s account username: Username @@ -236,14 +242,16 @@ en: destroy_custom_emoji: Delete Custom Emoji destroy_domain_allow: Delete Domain Allow destroy_domain_block: Delete Domain Block - destroy_email_domain_block: Delete e-mail domain block + destroy_email_domain_block: Delete E-mail Domain Block destroy_ip_block: Delete IP rule destroy_status: Delete Post destroy_unavailable_domain: Delete Unavailable Domain disable_2fa_user: Disable 2FA disable_custom_emoji: Disable Custom Emoji + disable_sign_in_token_auth_user: Disable E-mail Token Authentication for User disable_user: Disable User enable_custom_emoji: Enable Custom Emoji + enable_sign_in_token_auth_user: Enable E-mail Token Authentication for User enable_user: Enable User memorialize_account: Memorialize Account promote_user: Promote User @@ -251,12 +259,12 @@ en: reopen_report: Reopen Report reset_password_user: Reset Password resolve_report: Resolve Report - sensitive_account: Mark the media in your account as sensitive - silence_account: Silence Account + sensitive_account: Force-Sensitive Account + silence_account: Limit Account suspend_account: Suspend Account unassigned_report: Unassign Report - unsensitive_account: Unmark the media in your account as sensitive - unsilence_account: Unsilence Account + unsensitive_account: Undo Force-Sensitive Account + unsilence_account: Undo Limit Account unsuspend_account: Unsuspend Account update_announcement: Update Announcement update_custom_emoji: Update Custom Emoji @@ -285,8 +293,10 @@ en: destroy_unavailable_domain_html: "%{name} resumed delivery to domain %{target}" disable_2fa_user_html: "%{name} disabled two factor requirement for user %{target}" disable_custom_emoji_html: "%{name} disabled emoji %{target}" + disable_sign_in_token_auth_user_html: "%{name} disabled e-mail token authentication for %{target}" disable_user_html: "%{name} disabled login for user %{target}" enable_custom_emoji_html: "%{name} enabled emoji %{target}" + enable_sign_in_token_auth_user_html: "%{name} enabled e-mail token authentication for %{target}" enable_user_html: "%{name} enabled login for user %{target}" memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page" promote_user_html: "%{name} promoted user %{target}" @@ -295,11 +305,11 @@ en: reset_password_user_html: "%{name} reset password of user %{target}" resolve_report_html: "%{name} resolved report %{target}" sensitive_account_html: "%{name} marked %{target}'s media as sensitive" - silence_account_html: "%{name} silenced %{target}'s account" + silence_account_html: "%{name} limited %{target}'s account" suspend_account_html: "%{name} suspended %{target}'s account" unassigned_report_html: "%{name} unassigned report %{target}" unsensitive_account_html: "%{name} unmarked %{target}'s media as sensitive" - unsilence_account_html: "%{name} unsilenced %{target}'s account" + unsilence_account_html: "%{name} undid limit of %{target}'s account" unsuspend_account_html: "%{name} unsuspended %{target}'s account" update_announcement_html: "%{name} updated announcement %{target}" update_custom_emoji_html: "%{name} updated emoji %{target}" @@ -421,14 +431,14 @@ en: rejecting_media: rejecting media files rejecting_reports: rejecting reports severity: - silence: silenced + silence: limited suspend: suspended show: affected_accounts: one: One account in the database affected other: "%{count} accounts in the database affected" retroactive: - silence: Unsilence existing affected accounts from this domain + silence: Undo limit of existing affected accounts from this domain suspend: Unsuspend existing affected accounts from this domain title: Undo domain block for %{domain} undo: Undo diff --git a/config/routes.rb b/config/routes.rb index 4e70b98e2..a7687c62b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -285,6 +285,7 @@ Rails.application.routes.draw do resources :users, only: [] do resource :two_factor_authentication, only: [:destroy] + resource :sign_in_token_authentication, only: [:create, :destroy] end resources :custom_emojis, only: [:index, :new, :create] do diff --git a/db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb b/db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb new file mode 100644 index 000000000..43ad9b954 --- /dev/null +++ b/db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb @@ -0,0 +1,5 @@ +class AddSkipSignInTokenToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :skip_sign_in_token, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 6d848d5bd..6d8c5d4bf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -929,6 +929,7 @@ ActiveRecord::Schema.define(version: 2021_06_30_000137) do t.datetime "sign_in_token_sent_at" t.string "webauthn_id" t.inet "sign_up_ip" + t.boolean "skip_sign_in_token" t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id" diff --git a/dist/mastodon-sidekiq.service b/dist/mastodon-sidekiq.service index 9dd21b8a0..35b121cd7 100644 --- a/dist/mastodon-sidekiq.service +++ b/dist/mastodon-sidekiq.service @@ -9,6 +9,7 @@ WorkingDirectory=/home/mastodon/live Environment="RAILS_ENV=production" Environment="DB_POOL=25" Environment="MALLOC_ARENA_MAX=2" +Environment="LD_PRELOAD=libjemalloc.so" ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 25 TimeoutSec=15 Restart=always diff --git a/dist/mastodon-web.service b/dist/mastodon-web.service index c106a4860..f41efd2b0 100644 --- a/dist/mastodon-web.service +++ b/dist/mastodon-web.service @@ -8,6 +8,7 @@ User=mastodon WorkingDirectory=/home/mastodon/live Environment="RAILS_ENV=production" Environment="PORT=3000" +Environment="LD_PRELOAD=libjemalloc.so" ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb ExecReload=/bin/kill -SIGUSR1 $MAINPID TimeoutSec=15 diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 74162256f..050194801 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -54,7 +54,8 @@ module Mastodon option :email, required: true option :confirmed, type: :boolean - option :role, default: 'user' + option :role, default: 'user', enum: %w(user moderator admin) + option :skip_sign_in_token, type: :boolean option :reattach, type: :boolean option :force, type: :boolean desc 'create USERNAME', 'Create a new user' @@ -68,6 +69,9 @@ module Mastodon With the --role option one of "user", "admin" or "moderator" can be supplied. Defaults to "user" + With the --skip-sign-in-token option, you can ensure that + the user is never asked for an e-mailed security code. + With the --reattach option, the new user will be reattached to a given existing username of an old account. If the old account is still in use by someone else, you can supply @@ -77,7 +81,7 @@ module Mastodon def create(username) account = Account.new(username: username) password = SecureRandom.hex - user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true) + user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true, skip_sign_in_token: options[:skip_sign_in_token]) if options[:reattach] account = Account.find_local(username) || Account.new(username: username) @@ -113,7 +117,7 @@ module Mastodon end end - option :role + option :role, enum: %w(user moderator admin) option :email option :confirm, type: :boolean option :enable, type: :boolean @@ -121,6 +125,7 @@ module Mastodon option :disable_2fa, type: :boolean option :approve, type: :boolean option :reset_password, type: :boolean + option :skip_sign_in_token, type: :boolean desc 'modify USERNAME', 'Modify a user' long_desc <<-LONG_DESC Modify a user account. @@ -142,6 +147,9 @@ module Mastodon With the --reset-password option, the user's password is replaced by a randomly-generated one, printed in the output. + + With the --skip-sign-in-token option, you can ensure that + the user is never asked for an e-mailed security code. LONG_DESC def modify(username) user = Account.find_local(username)&.user @@ -163,6 +171,7 @@ module Mastodon user.disabled = true if options[:disable] user.approved = true if options[:approve] user.otp_required_for_login = false if options[:disable_2fa] + user.skip_sign_in_token = options[:skip_sign_in_token] unless options[:skip_sign_in_token].nil? user.confirm if options[:confirm] if user.save diff --git a/lib/mastodon/domains_cli.rb b/lib/mastodon/domains_cli.rb index 4ebd8a1e2..a7c78c4a7 100644 --- a/lib/mastodon/domains_cli.rb +++ b/lib/mastodon/domains_cli.rb @@ -17,6 +17,7 @@ module Mastodon option :verbose, type: :boolean, aliases: [:v] option :dry_run, type: :boolean option :limited_federation_mode, type: :boolean + option :by_uri, type: :boolean desc 'purge [DOMAIN...]', 'Remove accounts from a DOMAIN without a trace' long_desc <<-LONG_DESC Remove all accounts from a given DOMAIN without leaving behind any @@ -26,6 +27,12 @@ module Mastodon When the --limited-federation-mode option is given, instead of purging accounts from a single domain, all accounts from domains that have not been explicitly allowed are removed from the database. + + When the --by-uri option is given, DOMAIN is used to match the domain part of actor + URIs rather than the domain part of the webfinger handle. For instance, an account + that has the handle `foo@bar.com` but whose profile is at the URL + `https://mastodon-bar.com/users/foo`, would be purged by either + `tootctl domains purge bar.com` or `tootctl domains purge --by-uri mastodon-bar.com`. LONG_DESC def purge(*domains) dry_run = options[:dry_run] ? ' (DRY RUN)' : '' @@ -34,7 +41,11 @@ module Mastodon if options[:limited_federation_mode] Account.remote.where.not(domain: DomainAllow.pluck(:domain)) elsif !domains.empty? - Account.remote.where(domain: domains) + if options[:by_uri] + domains.map { |domain| Account.remote.where(Account.arel_table[:uri].matches("https://#{domain}/%", false, true)) }.reduce(:or) + else + Account.remote.where(domain: domains) + end else say('No domain(s) given', :red) exit(1) diff --git a/package.json b/package.json index 4cb8829cd..81abfa8bd 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@babel/runtime": "^7.14.6", "@gamestdio/websocket": "^0.3.2", "@github/webauthn-json": "^0.5.7", - "@rails/ujs": "^6.1.3", + "@rails/ujs": "^6.1.4", "array-includes": "^3.1.3", "atrament": "0.2.4", "arrow-key-navigation": "^1.2.0", @@ -171,14 +171,14 @@ "webpack-cli": "^3.3.12", "webpack-merge": "^5.8.0", "wicg-inert": "^3.1.1", - "ws": "^7.5.1" + "ws": "^7.5.2" }, "devDependencies": { "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "babel-eslint": "^10.1.0", "babel-jest": "^27.0.6", - "eslint": "^7.29.0", + "eslint": "^7.30.0", "eslint-plugin-import": "~2.23.4", "eslint-plugin-jsx-a11y": "~6.4.1", "eslint-plugin-promise": "~5.1.0", diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb index d23f2c17c..1722690db 100644 --- a/spec/controllers/activitypub/outboxes_controller_spec.rb +++ b/spec/controllers/activitypub/outboxes_controller_spec.rb @@ -55,6 +55,10 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do it_behaves_like 'cachable response' + it 'does not have a Vary header' do + expect(response.headers['Vary']).to be_nil + end + context 'when account is permanently suspended' do before do account.suspend! @@ -96,6 +100,10 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do it_behaves_like 'cachable response' + it 'returns Vary header with Signature' do + expect(response.headers['Vary']).to include 'Signature' + end + context 'when account is permanently suspended' do before do account.suspend! @@ -144,7 +152,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do end it 'returns private Cache-Control header' do - expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + expect(response.headers['Cache-Control']).to eq 'max-age=60, private' end end @@ -170,7 +178,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do end it 'returns private Cache-Control header' do - expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + expect(response.headers['Cache-Control']).to eq 'max-age=60, private' end end @@ -195,7 +203,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do end it 'returns private Cache-Control header' do - expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + expect(response.headers['Cache-Control']).to eq 'max-age=60, private' end end @@ -220,7 +228,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do end it 'returns private Cache-Control header' do - expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + expect(response.headers['Cache-Control']).to eq 'max-age=60, private' end end end diff --git a/spec/controllers/admin/resets_controller_spec.rb b/spec/controllers/admin/resets_controller_spec.rb index a20a460bd..c1e34b7f9 100644 --- a/spec/controllers/admin/resets_controller_spec.rb +++ b/spec/controllers/admin/resets_controller_spec.rb @@ -16,7 +16,7 @@ describe Admin::ResetsController do post :create, params: { account_id: account.id } - expect(response).to redirect_to(admin_accounts_path) + expect(response).to redirect_to(admin_account_path(account.id)) end end end diff --git a/spec/controllers/admin/two_factor_authentications_controller_spec.rb b/spec/controllers/admin/two_factor_authentications_controller_spec.rb index b0e82d3d6..c65095729 100644 --- a/spec/controllers/admin/two_factor_authentications_controller_spec.rb +++ b/spec/controllers/admin/two_factor_authentications_controller_spec.rb @@ -15,12 +15,12 @@ describe Admin::TwoFactorAuthenticationsController do user.update(otp_required_for_login: true) end - it 'redirects to admin accounts page' do + it 'redirects to admin account page' do delete :destroy, params: { user_id: user.id } user.reload expect(user.otp_enabled?).to eq false - expect(response).to redirect_to(admin_accounts_path) + expect(response).to redirect_to(admin_account_path(user.account_id)) end end @@ -38,13 +38,13 @@ describe Admin::TwoFactorAuthenticationsController do nickname: 'Security Key') end - it 'redirects to admin accounts page' do + it 'redirects to admin account page' do delete :destroy, params: { user_id: user.id } user.reload expect(user.otp_enabled?).to eq false expect(user.webauthn_enabled?).to eq false - expect(response).to redirect_to(admin_accounts_path) + expect(response).to redirect_to(admin_account_path(user.account_id)) end end end diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb index 1075456f3..8574d369d 100644 --- a/spec/controllers/well_known/webfinger_controller_spec.rb +++ b/spec/controllers/well_known/webfinger_controller_spec.rb @@ -24,6 +24,10 @@ describe WellKnown::WebfingerController, type: :controller do expect(response).to have_http_status(200) end + it 'does not set a Vary header' do + expect(response.headers['Vary']).to be_nil + end + it 'returns application/jrd+json' do expect(response.media_type).to eq 'application/jrd+json' end diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index 76277c467..45f7c3329 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -37,7 +37,7 @@ describe TagFeed, type: :service do expect(results).to include both end - it 'handles being passed non existant tag names' do + it 'handles being passed non existent tag names' do results = described_class.new(tag1, nil, any: ['wark']).get(20) expect(results).to include status1 expect(results).to_not include status2 diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5db249be2..54bb6db7f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -344,6 +344,34 @@ RSpec.describe User, type: :model do end end + describe '#reset_password!' do + subject(:user) { Fabricate(:user, password: 'foobar12345') } + + let!(:session_activation) { Fabricate(:session_activation, user: user) } + let!(:access_token) { Fabricate(:access_token, resource_owner_id: user.id) } + let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) } + + before do + user.reset_password! + end + + it 'changes the password immediately' do + expect(user.external_or_valid_password?('foobar12345')).to be false + end + + it 'deactivates all sessions' do + expect(user.session_activations.count).to eq 0 + end + + it 'revokes all access tokens' do + expect(Doorkeeper::AccessToken.active_for(user).count).to eq 0 + end + + it 'removes push subscriptions' do + expect(Web::PushSubscription.where(user: user).or(Web::PushSubscription.where(access_token: access_token)).count).to eq 0 + end + end + describe '#confirm!' do subject(:user) { Fabricate(:user, confirmed_at: confirmed_at) } diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb index 880ca4f0d..16f3e9962 100644 --- a/spec/services/bootstrap_timeline_service_spec.rb +++ b/spec/services/bootstrap_timeline_service_spec.rb @@ -1,4 +1,37 @@ require 'rails_helper' RSpec.describe BootstrapTimelineService, type: :service do + subject { BootstrapTimelineService.new } + + context 'when the new user has registered from an invite' do + let(:service) { double } + let(:autofollow) { false } + let(:inviter) { Fabricate(:user, confirmed_at: 2.days.ago) } + let(:invite) { Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now, autofollow: autofollow) } + let(:new_user) { Fabricate(:user, invite_code: invite.code) } + + before do + allow(FollowService).to receive(:new).and_return(service) + allow(service).to receive(:call) + end + + context 'when the invite has auto-follow enabled' do + let(:autofollow) { true } + + it 'calls FollowService to follow the inviter' do + subject.call(new_user.account) + expect(service).to have_received(:call).with(new_user.account, inviter.account) + end + end + + context 'when the invite does not have auto-follow enable' do + let(:autofollow) { false } + + it 'calls FollowService to follow the inviter' do + subject.call(new_user.account) + expect(service).to_not have_received(:call) + end + end + + end end diff --git a/yarn.lock b/yarn.lock index d4bc6a5cc..59dda7beb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,6 +1129,20 @@ resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-0.5.7.tgz#143bc67f6e0f75f8d188e565741507bb08c31214" integrity sha512-SUYsttDxFSvWvvJssJpwzjmRCqYfdfqC9VCmAHQYfdKCVelyJteCHo9/lK1CB72mx/jrl6cFNY08aua4J2jIyg== +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" + integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1370,10 +1384,10 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71" integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA== -"@rails/ujs@^6.1.3": - version "6.1.3" - resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.3.tgz#90ef26caa0925492b1a3b1495db09cfbe49e745e" - integrity sha512-9mip5o+LVouWAqLMNJWhxda+D5uP+4RziNECgOGJlL6k3rc5SC/ljCHpV9Cym4i3oeGZkpZJ2tu4frCwt84kzQ== +"@rails/ujs@^6.1.4": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.4.tgz#093d5341595a02089ed309dec40f3c37da7b1b10" + integrity sha512-O3lEzL5DYbxppMdsFSw36e4BHIlfz/xusynwXGv3l2lhSlvah41qviRpsoAlKXxl37nZAqK+UUF5cnGGK45Mfw== "@sinonjs/commons@^1.7.0": version "1.8.1" @@ -4441,13 +4455,14 @@ eslint@^2.7.0: text-table "~0.2.0" user-home "^2.0.0" -eslint@^7.29.0: - version "7.29.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.29.0.tgz#ee2a7648f2e729485e4d0bd6383ec1deabc8b3c0" - integrity sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA== +eslint@^7.30.0: + version "7.30.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.30.0.tgz#6d34ab51aaa56112fd97166226c9a97f505474f8" + integrity sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg== dependencies: "@babel/code-frame" "7.12.11" "@eslint/eslintrc" "^0.4.2" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -11715,10 +11730,10 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.2.3, ws@^7.3.1, ws@^7.5.1: - version "7.5.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.1.tgz#44fc000d87edb1d9c53e51fbc69a0ac1f6871d66" - integrity sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow== +ws@^7.2.3, ws@^7.3.1, ws@^7.5.2: + version "7.5.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.2.tgz#09cc8fea3bec1bc5ed44ef51b42f945be36900f6" + integrity sha512-lkF7AWRicoB9mAgjeKbGqVUekLnSNO4VjKVnuPHpQeOxZOErX6BPXwJk70nFslRCEEA8EVW7ZjKwXaP9N+1sKQ== xml-name-validator@^3.0.0: version "3.0.0"