Merge branch 'master' into glitch-soc/merge-upstream
Conflicts:
- `.github/dependabot.yml`:
Updated upstream, we deleted it to not be flooded by Depandabot.
Kept deleted.
- `Gemfile.lock`:
Puma updated on both sides, went for the most recent version.
- `app/controllers/api/v1/mutes_controller.rb`:
Upstream updated the serializer to support timed mutes, while
glitch-soc added a custom API ages ago to get information that
is already available elsewhere.
Dropped the glitch-soc-specific API, went with upstream changes.
- `app/javascript/core/admin.js`:
Conflict due to changing how assets are loaded. Went with upstream.
- `app/javascript/packs/public.js`:
Conflict due to changing how assets are loaded. Went with upstream.
- `app/models/mute.rb`:
🤷
- `app/models/user.rb`:
New user setting added upstream while we have glitch-soc-specific
user settings. Added upstream's user setting.
- `config/settings.yml`:
Upstream added a new user setting close to a user setting we had
changed the defaults for. Added the new upstream setting.
- `package.json`:
Upstream dependency updated “too close” to a glitch-soc-specific
dependency. No real conflict. Updated the dependency.
This commit is contained in:
@@ -354,6 +354,12 @@ class Account < ApplicationRecord
|
||||
shared_inbox_url.presence || inbox_url
|
||||
end
|
||||
|
||||
def synchronization_uri_prefix
|
||||
return 'local' if local?
|
||||
|
||||
@synchronization_uri_prefix ||= uri[/http(s?):\/\/[^\/]+\//]
|
||||
end
|
||||
|
||||
class Field < ActiveModelSerializers::Model
|
||||
attributes :name, :value, :verified_at, :account, :errors
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class AccountAlias < ApplicationRecord
|
||||
def set_uri
|
||||
target_account = ResolveAccountService.new.call(acct)
|
||||
self.uri = ActivityPub::TagManager.instance.uri_for(target_account) unless target_account.nil?
|
||||
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
||||
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
||||
# Validation will take care of it
|
||||
end
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class AccountMigration < ApplicationRecord
|
||||
|
||||
def set_target_account
|
||||
self.target_account = ResolveAccountService.new.call(acct)
|
||||
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
||||
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
||||
# Validation will take care of it
|
||||
end
|
||||
|
||||
|
||||
@@ -131,9 +131,12 @@ module AccountInteractions
|
||||
.find_or_create_by!(target_account: other_account)
|
||||
end
|
||||
|
||||
def mute!(other_account, notifications: nil)
|
||||
def mute!(other_account, notifications: nil, duration: 0)
|
||||
notifications = true if notifications.nil?
|
||||
mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account)
|
||||
mute = mute_relationships.create_with(hide_notifications: notifications).find_or_initialize_by(target_account: other_account)
|
||||
mute.expires_in = duration.zero? ? nil : duration
|
||||
mute.save!
|
||||
|
||||
remove_potential_friendship(other_account)
|
||||
|
||||
# When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
|
||||
@@ -240,6 +243,26 @@ module AccountInteractions
|
||||
.where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
|
||||
end
|
||||
|
||||
def remote_followers_hash(url_prefix)
|
||||
Rails.cache.fetch("followers_hash:#{id}:#{url_prefix}") do
|
||||
digest = "\x00" * 32
|
||||
followers.where(Account.arel_table[:uri].matches(url_prefix + '%', false, true)).pluck_each(:uri) do |uri|
|
||||
Xorcist.xor!(digest, Digest::SHA256.digest(uri))
|
||||
end
|
||||
digest.unpack('H*')[0]
|
||||
end
|
||||
end
|
||||
|
||||
def local_followers_hash
|
||||
Rails.cache.fetch("followers_hash:#{id}:local") do
|
||||
digest = "\x00" * 32
|
||||
followers.where(domain: nil).pluck_each(:username) do |username|
|
||||
Xorcist.xor!(digest, Digest::SHA256.digest(ActivityPub::TagManager.instance.uri_for_username(username)))
|
||||
end
|
||||
digest.unpack('H*')[0]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove_potential_friendship(other_account, mutual = false)
|
||||
|
||||
@@ -6,7 +6,15 @@ module Expireable
|
||||
included do
|
||||
scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) }
|
||||
|
||||
attr_reader :expires_in
|
||||
def expires_in
|
||||
return @expires_in if defined?(@expires_in)
|
||||
|
||||
if expires_at.nil?
|
||||
nil
|
||||
else
|
||||
(expires_at - created_at).to_i
|
||||
end
|
||||
end
|
||||
|
||||
def expires_in=(interval)
|
||||
self.expires_at = interval.to_i.seconds.from_now if interval.present?
|
||||
|
||||
@@ -41,8 +41,10 @@ class Follow < ApplicationRecord
|
||||
|
||||
before_validation :set_uri, only: :create
|
||||
after_create :increment_cache_counters
|
||||
after_create :invalidate_hash_cache
|
||||
after_destroy :remove_endorsements
|
||||
after_destroy :decrement_cache_counters
|
||||
after_destroy :invalidate_hash_cache
|
||||
|
||||
private
|
||||
|
||||
@@ -63,4 +65,10 @@ class Follow < ApplicationRecord
|
||||
account&.decrement_count!(:following_count)
|
||||
target_account&.decrement_count!(:followers_count)
|
||||
end
|
||||
|
||||
def invalidate_hash_cache
|
||||
return if account.local? && target_account.local?
|
||||
|
||||
Rails.cache.delete("followers_hash:#{target_account_id}:#{account.synchronization_uri_prefix}")
|
||||
end
|
||||
end
|
||||
|
||||
31
app/models/form/ip_block_batch.rb
Normal file
31
app/models/form/ip_block_batch.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Form::IpBlockBatch
|
||||
include ActiveModel::Model
|
||||
include Authorization
|
||||
include AccountableConcern
|
||||
|
||||
attr_accessor :ip_block_ids, :action, :current_account
|
||||
|
||||
def save
|
||||
case action
|
||||
when 'delete'
|
||||
delete!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ip_blocks
|
||||
@ip_blocks ||= IpBlock.where(id: ip_block_ids)
|
||||
end
|
||||
|
||||
def delete!
|
||||
ip_blocks.each { |ip_block| authorize(ip_block, :destroy?) }
|
||||
|
||||
ip_blocks.each do |ip_block|
|
||||
ip_block.destroy
|
||||
log_action :destroy, ip_block
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -32,7 +32,7 @@ class Form::Redirect
|
||||
|
||||
def set_target_account
|
||||
@target_account = ResolveAccountService.new.call(acct)
|
||||
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
||||
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
||||
# Validation will take care of it
|
||||
end
|
||||
|
||||
|
||||
41
app/models/ip_block.rb
Normal file
41
app/models/ip_block.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: ip_blocks
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# expires_at :datetime
|
||||
# ip :inet default(#<IPAddr: IPv4:0.0.0.0/255.255.255.255>), not null
|
||||
# severity :integer default(NULL), not null
|
||||
# comment :text default(""), not null
|
||||
#
|
||||
|
||||
class IpBlock < ApplicationRecord
|
||||
CACHE_KEY = 'blocked_ips'
|
||||
|
||||
include Expireable
|
||||
|
||||
enum severity: {
|
||||
sign_up_requires_approval: 5000,
|
||||
no_access: 9999,
|
||||
}
|
||||
|
||||
validates :ip, :severity, presence: true
|
||||
|
||||
after_commit :reset_cache
|
||||
|
||||
class << self
|
||||
def blocked?(remote_ip)
|
||||
blocked_ips_map = Rails.cache.fetch(CACHE_KEY) { FastIpMap.new(IpBlock.where(severity: :no_access).pluck(:ip)) }
|
||||
blocked_ips_map.include?(remote_ip)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reset_cache
|
||||
Rails.cache.delete(CACHE_KEY)
|
||||
end
|
||||
end
|
||||
@@ -9,11 +9,14 @@
|
||||
# hide_notifications :boolean default(TRUE), not null
|
||||
# account_id :bigint(8) not null
|
||||
# target_account_id :bigint(8) not null
|
||||
# hide_notifications :boolean default(TRUE), not null
|
||||
# expires_at :datetime
|
||||
#
|
||||
|
||||
class Mute < ApplicationRecord
|
||||
include Paginable
|
||||
include RelationshipCacheable
|
||||
include Expireable
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :target_account, class_name: 'Account'
|
||||
|
||||
@@ -56,7 +56,7 @@ class RemoteFollow
|
||||
|
||||
if domain.nil?
|
||||
@addressable_template = Addressable::Template.new("#{authorize_interaction_url}?uri={uri}")
|
||||
elsif redirect_url_link.nil? || redirect_url_link.template.nil?
|
||||
elsif redirect_uri_template.nil?
|
||||
missing_resource_error
|
||||
else
|
||||
@addressable_template = Addressable::Template.new(redirect_uri_template)
|
||||
@@ -64,16 +64,12 @@ class RemoteFollow
|
||||
end
|
||||
|
||||
def redirect_uri_template
|
||||
redirect_url_link.template
|
||||
end
|
||||
|
||||
def redirect_url_link
|
||||
acct_resource&.link('http://ostatus.org/schema/1.0/subscribe')
|
||||
acct_resource&.link('http://ostatus.org/schema/1.0/subscribe', 'template')
|
||||
end
|
||||
|
||||
def acct_resource
|
||||
@acct_resource ||= webfinger!("acct:#{acct}")
|
||||
rescue Goldfinger::Error, HTTP::ConnectionError
|
||||
rescue Webfinger::Error, HTTP::ConnectionError
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
# sign_in_token :string
|
||||
# sign_in_token_sent_at :datetime
|
||||
# webauthn_id :string
|
||||
# sign_up_ip :inet
|
||||
#
|
||||
|
||||
class User < ApplicationRecord
|
||||
@@ -97,7 +98,7 @@ class User < ApplicationRecord
|
||||
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
|
||||
scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) }
|
||||
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
|
||||
scope :matches_ip, ->(value) { left_joins(:session_activations).where('users.current_sign_in_ip <<= ?', value).or(left_joins(:session_activations).where('users.last_sign_in_ip <<= ?', value)).or(left_joins(:session_activations).where('session_activations.ip <<= ?', value)) }
|
||||
scope :matches_ip, ->(value) { left_joins(:session_activations).where('users.current_sign_in_ip <<= ?', value).or(left_joins(:session_activations).where('users.sign_up_ip <<= ?', value)).or(left_joins(:session_activations).where('users.last_sign_in_ip <<= ?', value)).or(left_joins(:session_activations).where('session_activations.ip <<= ?', value)) }
|
||||
scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
|
||||
|
||||
before_validation :sanitize_languages
|
||||
@@ -115,7 +116,7 @@ class User < ApplicationRecord
|
||||
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network, :hide_followers_count,
|
||||
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
||||
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
|
||||
:default_content_type, :system_emoji_font,
|
||||
:disable_swiping, :default_content_type, :system_emoji_font,
|
||||
to: :settings, prefix: :setting, allow_nil: false
|
||||
|
||||
attr_reader :invite_code, :sign_in_token_attempt
|
||||
@@ -331,6 +332,7 @@ class User < ApplicationRecord
|
||||
|
||||
arr << [current_sign_in_at, current_sign_in_ip] if current_sign_in_ip.present?
|
||||
arr << [last_sign_in_at, last_sign_in_ip] if last_sign_in_ip.present?
|
||||
arr << [created_at, sign_up_ip] if sign_up_ip.present?
|
||||
|
||||
arr.sort_by { |pair| pair.first || Time.now.utc }.uniq(&:last).reverse!
|
||||
end
|
||||
@@ -385,7 +387,17 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def set_approved
|
||||
self.approved = open_registrations? || valid_invitation? || external?
|
||||
self.approved = begin
|
||||
if sign_up_from_ip_requires_approval?
|
||||
false
|
||||
else
|
||||
open_registrations? || valid_invitation? || external?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sign_up_from_ip_requires_approval?
|
||||
!sign_up_ip.nil? && IpBlock.where(severity: :sign_up_requires_approval).where('ip >>= ?', sign_up_ip.to_s).exists?
|
||||
end
|
||||
|
||||
def open_registrations?
|
||||
|
||||
Reference in New Issue
Block a user