forked from treehouse/mastodon
Merge pull request #1562 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changesrebase/4.0.0rc2
commit
f040b9aee3
4
Gemfile
4
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'
|
||||
|
|
128
Gemfile.lock
128
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1095,6 +1095,7 @@ a.status-card.compact:hover {
|
|||
&__account {
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.account__avatar {
|
||||
|
|
|
@ -7291,6 +7291,7 @@ noscript {
|
|||
&__account {
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.account__avatar {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
class AccountStat < ApplicationRecord
|
||||
self.locking_column = nil
|
||||
self.ignored_columns = %w(lock_version)
|
||||
|
||||
belongs_to :account, inverse_of: :account_stat
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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') } *
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddSkipSignInTokenToUsers < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :users, :skip_sign_in_token, :boolean
|
||||
end
|
||||
end
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
39
yarn.lock
39
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"
|
||||
|
|
Loading…
Reference in New Issue