Merge remote-tracking branch 'parent/main' into upstream-20230105

This commit is contained in:
KMY 2024-01-05 10:01:36 +09:00
commit a0a3d1b101
65 changed files with 1008 additions and 453 deletions

View file

@ -75,7 +75,6 @@ Metrics/AbcSize:
Exclude: Exclude:
- 'app/serializers/initial_state_serializer.rb' - 'app/serializers/initial_state_serializer.rb'
- 'lib/mastodon/cli/*.rb' - 'lib/mastodon/cli/*.rb'
- db/*migrate/**/*
# Reason: Currently disabled in .rubocop_todo.yml # Reason: Currently disabled in .rubocop_todo.yml
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity # https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity
@ -87,7 +86,6 @@ Metrics/CyclomaticComplexity:
- 'app/services/delivery_antenna_service.rb' - 'app/services/delivery_antenna_service.rb'
- 'app/services/post_status_service.rb' - 'app/services/post_status_service.rb'
- lib/mastodon/cli/*.rb - lib/mastodon/cli/*.rb
- db/*migrate/**/*
# Reason: # Reason:
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists # https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists

View file

@ -167,7 +167,6 @@ Rails/WhereExists:
- 'app/validators/reaction_validator.rb' - 'app/validators/reaction_validator.rb'
- 'app/validators/vote_validator.rb' - 'app/validators/vote_validator.rb'
- 'app/workers/move_worker.rb' - 'app/workers/move_worker.rb'
- 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb'
- 'lib/tasks/tests.rake' - 'lib/tasks/tests.rake'
- 'spec/models/account_spec.rb' - 'spec/models/account_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb' - 'spec/services/activitypub/process_collection_service_spec.rb'
@ -255,8 +254,6 @@ Style/GuardClause:
- 'app/workers/redownload_media_worker.rb' - 'app/workers/redownload_media_worker.rb'
- 'app/workers/remote_account_refresh_worker.rb' - 'app/workers/remote_account_refresh_worker.rb'
- 'config/initializers/devise.rb' - 'config/initializers/devise.rb'
- 'db/migrate/20170901141119_truncate_preview_cards.rb'
- 'db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb'
- 'lib/devise/strategies/two_factor_ldap_authenticatable.rb' - 'lib/devise/strategies/two_factor_ldap_authenticatable.rb'
- 'lib/devise/strategies/two_factor_pam_authenticatable.rb' - 'lib/devise/strategies/two_factor_pam_authenticatable.rb'
- 'lib/mastodon/cli/accounts.rb' - 'lib/mastodon/cli/accounts.rb'
@ -277,7 +274,6 @@ Style/HashAsLastArrayItem:
- 'app/models/status.rb' - 'app/models/status.rb'
- 'app/services/batched_remove_status_service.rb' - 'app/services/batched_remove_status_service.rb'
- 'app/services/notify_service.rb' - 'app/services/notify_service.rb'
- 'db/migrate/20181024224956_migrate_account_conversations.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
Style/HashTransformValues: Style/HashTransformValues:

View file

@ -474,7 +474,7 @@ GEM
net-imap (0.4.4) net-imap (0.4.4)
date date
net-protocol net-protocol
net-ldap (0.18.0) net-ldap (0.19.0)
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
@ -678,7 +678,7 @@ GEM
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.30.0) rubocop-ast (1.30.0)
parser (>= 3.2.1.0) parser (>= 3.2.1.0)
rubocop-capybara (2.19.0) rubocop-capybara (2.20.0)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-factory_bot (2.24.0) rubocop-factory_bot (2.24.0)
rubocop (~> 1.33) rubocop (~> 1.33)

View file

@ -40,7 +40,7 @@ module Admin
(@email_domain_block.other_domains || []).uniq.each do |domain| (@email_domain_block.other_domains || []).uniq.each do |domain|
next if EmailDomainBlock.where(domain: domain).exists? next if EmailDomainBlock.where(domain: domain).exists?
other_email_domain_block = EmailDomainBlock.create!(domain: domain, parent: @email_domain_block) other_email_domain_block = EmailDomainBlock.create!(domain: domain, allow_with_approval: @email_domain_block.allow_with_approval, parent: @email_domain_block)
log_action :create, other_email_domain_block log_action :create, other_email_domain_block
end end
end end
@ -65,7 +65,7 @@ module Admin
end end
def resource_params def resource_params
params.require(:email_domain_block).permit(:domain, other_domains: []) params.require(:email_domain_block).permit(:domain, :allow_with_approval, other_domains: [])
end end
def form_email_domain_block_batch_params def form_email_domain_block_batch_params

View file

@ -68,7 +68,7 @@ module Admin
def export_data def export_data
CSV.generate(headers: export_headers, write_headers: true) do |content| CSV.generate(headers: export_headers, write_headers: true) do |content|
DomainBlock.with_limitations.each do |instance| DomainBlock.with_limitations.order(id: :asc).each do |instance|
content << [instance.domain, instance.severity, instance.reject_media, instance.reject_reports, instance.public_comment, instance.obfuscate] content << [instance.domain, instance.severity, instance.reject_media, instance.reject_reports, instance.public_comment, instance.obfuscate]
end end
end end

View file

@ -55,7 +55,7 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
end end
def resource_params def resource_params
params.permit(:domain) params.permit(:domain, :allow_with_approval)
end end
def insert_pagination_headers def insert_pagination_headers

View file

@ -91,14 +91,23 @@ module SignatureVerification
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil? raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
signature = Base64.decode64(signature_params['signature']) signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string compare_signed_string = build_signed_string(include_query_string: true)
return actor unless verify_signature(actor, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
# Compatibility quirk with older Mastodon versions
compare_signed_string = build_signed_string(include_query_string: false)
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
actor = stoplight_wrap_request { actor_refresh_key!(actor) } actor = stoplight_wrap_request { actor_refresh_key!(actor) }
raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil? raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
compare_signed_string = build_signed_string(include_query_string: true)
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
# Compatibility quirk with older Mastodon versions
compare_signed_string = build_signed_string(include_query_string: false)
return actor unless verify_signature(actor, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature'] fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
@ -180,11 +189,18 @@ module SignatureVerification
nil nil
end end
def build_signed_string def build_signed_string(include_query_string: true)
signed_headers.map do |signed_header| signed_headers.map do |signed_header|
case signed_header case signed_header
when Request::REQUEST_TARGET when Request::REQUEST_TARGET
if include_query_string
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
else
# Current versions of Mastodon incorrectly omit the query string from the (request-target) pseudo-header.
# Therefore, temporarily support such incorrect signatures for compatibility.
# TODO: remove eventually some time after release of the fixed version
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}" "#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
end
when '(created)' when '(created)'
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019' raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank? raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?

View file

@ -21,7 +21,7 @@ module WellKnown
username = username_from_resource username = username_from_resource
@account = begin @account = begin
if username == Rails.configuration.x.local_domain if username == Rails.configuration.x.local_domain || username == Rails.configuration.x.web_domain
Account.representative Account.representative
else else
Account.find_local!(username) Account.find_local!(username)

View file

@ -654,16 +654,20 @@ class Status extends ImmutablePureComponent {
)); ));
} }
setRef = c => { setContainerRef = c => {
this.node = c; this.node = c;
}; };
setStatusRef = c => {
this.statusNode = c;
};
_scrollStatusIntoView () { _scrollStatusIntoView () {
const { status, multiColumn } = this.props; const { status, multiColumn } = this.props;
if (status) { if (status) {
window.requestAnimationFrame(() => { requestIdleCallback(() => {
this.node?.querySelector('.detailed-status__wrapper')?.scrollIntoView(true); this.statusNode?.scrollIntoView(true);
// In the single-column interface, `scrollIntoView` will put the post behind the header, // In the single-column interface, `scrollIntoView` will put the post behind the header,
// so compensate for that. // so compensate for that.
@ -701,9 +705,8 @@ class Status extends ImmutablePureComponent {
} }
// Scroll to focused post if it is loaded // Scroll to focused post if it is loaded
const child = this.node?.querySelector('.detailed-status__wrapper'); if (this.statusNode) {
if (child) { return [0, this.statusNode.offsetTop];
return [0, child.offsetTop];
} }
// Do not scroll otherwise, `componentDidUpdate` will take care of that // Do not scroll otherwise, `componentDidUpdate` will take care of that
@ -768,12 +771,12 @@ class Status extends ImmutablePureComponent {
/> />
<ScrollContainer scrollKey='thread' shouldUpdateScroll={this.shouldUpdateScroll}> <ScrollContainer scrollKey='thread' shouldUpdateScroll={this.shouldUpdateScroll}>
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}> <div className={classNames('scrollable', { fullscreen })} ref={this.setContainerRef}>
{references} {references}
{ancestors} {ancestors}
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)}> <div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)} ref={this.setStatusRef}>
<DetailedStatus <DetailedStatus
key={`details-${status.get('id')}`} key={`details-${status.get('id')}`}
status={status} status={status}

View file

@ -38,12 +38,14 @@
"confirmation_modal.cancel": "Cancellar", "confirmation_modal.cancel": "Cancellar",
"confirmations.delete.confirm": "Deler", "confirmations.delete.confirm": "Deler",
"confirmations.delete_list.confirm": "Deler", "confirmations.delete_list.confirm": "Deler",
"confirmations.edit.confirm": "Modificar",
"confirmations.logout.confirm": "Clauder le session", "confirmations.logout.confirm": "Clauder le session",
"copy_icon_button.copied": "Copiate al area de transferentia", "copy_icon_button.copied": "Copiate al area de transferentia",
"copypaste.copy_to_clipboard": "Copiar al area de transferentia", "copypaste.copy_to_clipboard": "Copiar al area de transferentia",
"disabled_account_banner.account_settings": "Parametros de conto", "disabled_account_banner.account_settings": "Parametros de conto",
"dismissable_banner.dismiss": "Dimitter", "dismissable_banner.dismiss": "Dimitter",
"emoji_button.activity": "Activitate", "emoji_button.activity": "Activitate",
"emoji_button.clear": "Rader",
"emoji_button.custom": "Personalisate", "emoji_button.custom": "Personalisate",
"emoji_button.search_results": "Resultatos de recerca", "emoji_button.search_results": "Resultatos de recerca",
"empty_column.account_unavailable": "Profilo non disponibile", "empty_column.account_unavailable": "Profilo non disponibile",
@ -69,16 +71,37 @@
"navigation_bar.about": "A proposito de", "navigation_bar.about": "A proposito de",
"navigation_bar.advanced_interface": "Aperir in un interfacie web avantiate", "navigation_bar.advanced_interface": "Aperir in un interfacie web avantiate",
"navigation_bar.blocks": "Usatores blocate", "navigation_bar.blocks": "Usatores blocate",
"navigation_bar.discover": "Discoperir",
"navigation_bar.edit_profile": "Modificar profilo",
"navigation_bar.favourites": "Favoritos", "navigation_bar.favourites": "Favoritos",
"navigation_bar.lists": "Listas", "navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session", "navigation_bar.logout": "Clauder le session",
"navigation_bar.preferences": "Preferentias", "navigation_bar.preferences": "Preferentias",
"navigation_bar.search": "Cercar",
"navigation_bar.security": "Securitate", "navigation_bar.security": "Securitate",
"notifications.column_settings.alert": "Notificationes de scriptorio", "notifications.column_settings.alert": "Notificationes de scriptorio",
"notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias", "notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias",
"notifications.column_settings.sound": "Reproducer sono", "notifications.column_settings.sound": "Reproducer sono",
"notifications.filter.all": "Toto", "notifications.filter.all": "Toto",
"notifications.grant_permission": "Conceder permission.",
"notifications.group": "{count} notificationes",
"onboarding.compose.template": "Salute #Mastodon!", "onboarding.compose.template": "Salute #Mastodon!",
"onboarding.profile.save_and_continue": "Salvar e continuar", "onboarding.profile.save_and_continue": "Salvar e continuar",
"onboarding.share.title": "Compartir tu profilo" "onboarding.share.title": "Compartir tu profilo",
"onboarding.steps.share_profile.title": "Compartir tu profilo de Mastodon",
"relative_time.just_now": "ora",
"relative_time.today": "hodie",
"reply_indicator.cancel": "Cancellar",
"report.next": "Sequente",
"report.placeholder": "Commentos additional",
"report.reasons.dislike": "Non me place",
"search.quick_action.go_to_account": "Vader al profilo {x}",
"search_results.accounts": "Profilos",
"search_results.see_all": "Vider toto",
"status.delete": "Deler",
"status.share": "Compartir",
"status.translate": "Traducer",
"status.translated_from_with": "Traducite ab {lang} usante {provider}",
"tabs_bar.home": "Initio",
"tabs_bar.notifications": "Notificationes"
} }

View file

@ -501,6 +501,7 @@
"onboarding.steps.setup_profile.title": "Personaliza tu profil", "onboarding.steps.setup_profile.title": "Personaliza tu profil",
"onboarding.steps.share_profile.body": "Informe a tus amigos komo toparte en Mastodon", "onboarding.steps.share_profile.body": "Informe a tus amigos komo toparte en Mastodon",
"onboarding.steps.share_profile.title": "Partaja tu profil de Mastodon", "onboarding.steps.share_profile.title": "Partaja tu profil de Mastodon",
"password_confirmation.mismatching": "Los dos kodes son desferentes",
"picture_in_picture.restore": "Restora", "picture_in_picture.restore": "Restora",
"poll.closed": "Serrado", "poll.closed": "Serrado",
"poll.refresh": "Arefreska", "poll.refresh": "Arefreska",

View file

@ -606,7 +606,7 @@
"search.quick_action.status_search": "Innlegg som samsvarer med {x}", "search.quick_action.status_search": "Innlegg som samsvarer med {x}",
"search.search_or_paste": "Søk eller lim inn URL", "search.search_or_paste": "Søk eller lim inn URL",
"search_popout.full_text_search_disabled_message": "Ikkje tilgjengeleg på {domain}.", "search_popout.full_text_search_disabled_message": "Ikkje tilgjengeleg på {domain}.",
"search_popout.full_text_search_logged_out_message": "Bare tilgjengelig ved innlogging.", "search_popout.full_text_search_logged_out_message": "Bare tilgjengelig når man er logget inn.",
"search_popout.language_code": "ISO-språkkode", "search_popout.language_code": "ISO-språkkode",
"search_popout.options": "Søkjealternativ", "search_popout.options": "Søkjealternativ",
"search_popout.quick_actions": "Hurtighandlinger", "search_popout.quick_actions": "Hurtighandlinger",

View file

@ -606,7 +606,7 @@
"search.quick_action.status_search": "Innlegg som samsvarer med {x}", "search.quick_action.status_search": "Innlegg som samsvarer med {x}",
"search.search_or_paste": "Søk eller lim inn URL", "search.search_or_paste": "Søk eller lim inn URL",
"search_popout.full_text_search_disabled_message": "Ikke tilgjengelig på {domain}.", "search_popout.full_text_search_disabled_message": "Ikke tilgjengelig på {domain}.",
"search_popout.full_text_search_logged_out_message": "Bare tilgjengelig ved innlogging.", "search_popout.full_text_search_logged_out_message": "Bare tilgjengelig når man er logget inn.",
"search_popout.language_code": "ISO språkkode", "search_popout.language_code": "ISO språkkode",
"search_popout.options": "Alternativer for søk", "search_popout.options": "Alternativer for søk",
"search_popout.quick_actions": "Hurtighandlinger", "search_popout.quick_actions": "Hurtighandlinger",

View file

@ -77,6 +77,7 @@ class Request
@url = Addressable::URI.parse(url).normalize @url = Addressable::URI.parse(url).normalize
@http_client = options.delete(:http_client) @http_client = options.delete(:http_client)
@allow_local = options.delete(:allow_local) @allow_local = options.delete(:allow_local)
@full_path = options.delete(:with_query_string)
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket) @options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
@options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT) @options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT)
@options = @options.merge(proxy_url) if use_proxy? @options = @options.merge(proxy_url) if use_proxy?
@ -146,7 +147,7 @@ class Request
private private
def set_common_headers! def set_common_headers!
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}" @headers[REQUEST_TARGET] = request_target
@headers['User-Agent'] = Mastodon::Version.user_agent @headers['User-Agent'] = Mastodon::Version.user_agent
@headers['Host'] = @url.host @headers['Host'] = @url.host
@headers['Date'] = Time.now.utc.httpdate @headers['Date'] = Time.now.utc.httpdate
@ -157,6 +158,14 @@ class Request
@headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}" @headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
end end
def request_target
if @url.query.nil? || !@full_path
"#{@verb} #{@url.path}"
else
"#{@verb} #{@url.path}?#{@url.query}"
end
end
def signature def signature
algorithm = 'rsa-sha256' algorithm = 'rsa-sha256'
signature = Base64.strict_encode64(@keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) signature = Base64.strict_encode64(@keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string))

View file

@ -11,11 +11,12 @@ module Attachmentable
# For some file extensions, there exist different content # For some file extensions, there exist different content
# type variants, and browsers often send the wrong one, # type variants, and browsers often send the wrong one,
# for example, sending an audio .ogg file as video/ogg, # for example, sending an audio .ogg file as video/ogg,
# likewise, MimeMagic also misreports them as such. For # likewise, kt-paperclip also misreports them as such. For
# those files, it is necessary to use the output of the # those files, it is necessary to use the output of the
# `file` utility instead # `file` utility instead
INCORRECT_CONTENT_TYPES = %w( INCORRECT_CONTENT_TYPES = %w(
audio/vorbis audio/vorbis
audio/opus
video/ogg video/ogg
video/webm video/webm
).freeze ).freeze

View file

@ -9,6 +9,7 @@
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# parent_id :bigint(8) # parent_id :bigint(8)
# allow_with_approval :boolean default(FALSE), not null
# #
class EmailDomainBlock < ApplicationRecord class EmailDomainBlock < ApplicationRecord
@ -42,8 +43,8 @@ class EmailDomainBlock < ApplicationRecord
@attempt_ip = attempt_ip @attempt_ip = attempt_ip
end end
def match? def match?(...)
blocking? || invalid_uri? blocking?(...) || invalid_uri?
end end
private private
@ -52,8 +53,8 @@ class EmailDomainBlock < ApplicationRecord
@uris.any?(&:nil?) @uris.any?(&:nil?)
end end
def blocking? def blocking?(allow_with_approval: false)
blocks = EmailDomainBlock.where(domain: domains_with_variants).order(Arel.sql('char_length(domain) desc')) blocks = EmailDomainBlock.where(domain: domains_with_variants, allow_with_approval: allow_with_approval).order(Arel.sql('char_length(domain) desc'))
blocks.each { |block| block.history.add(@attempt_ip) } if @attempt_ip.present? blocks.each { |block| block.history.add(@attempt_ip) } if @attempt_ip.present?
blocks.any? blocks.any?
end end
@ -86,4 +87,8 @@ class EmailDomainBlock < ApplicationRecord
def self.block?(domain_or_domains, attempt_ip: nil) def self.block?(domain_or_domains, attempt_ip: nil)
Matcher.new(domain_or_domains, attempt_ip: attempt_ip).match? Matcher.new(domain_or_domains, attempt_ip: attempt_ip).match?
end end
def self.requires_approval?(domain_or_domains, attempt_ip: nil)
Matcher.new(domain_or_domains, attempt_ip: attempt_ip).match?(allow_with_approval: true)
end
end end

View file

@ -420,7 +420,7 @@ class User < ApplicationRecord
def set_approved def set_approved
self.approved = begin self.approved = begin
if sign_up_from_ip_requires_approval? if sign_up_from_ip_requires_approval? || sign_up_email_requires_approval?
false false
else else
open_registrations? || valid_invitation? || external? open_registrations? || valid_invitation? || external?
@ -432,6 +432,12 @@ class User < ApplicationRecord
!sign_up_ip.nil? && IpBlock.where(severity: :sign_up_requires_approval).where('ip >>= ?', sign_up_ip.to_s).exists? !sign_up_ip.nil? && IpBlock.where(severity: :sign_up_requires_approval).where('ip >>= ?', sign_up_ip.to_s).exists?
end end
def sign_up_email_requires_approval?
return false unless email.present? || unconfirmed_email.present?
EmailDomainBlock.requires_approval?(email.presence || unconfirmed_email, attempt_ip: sign_up_ip)
end
def open_registrations? def open_registrations?
Setting.registrations_mode == 'open' Setting.registrations_mode == 'open'
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::Admin::EmailDomainBlockSerializer < ActiveModel::Serializer class REST::Admin::EmailDomainBlockSerializer < ActiveModel::Serializer
attributes :id, :domain, :created_at, :history attributes :id, :domain, :created_at, :history, :allow_with_approval
def id def id
object.id.to_s object.id.to_s

View file

@ -12,3 +12,7 @@
· ·
= t('admin.email_domain_blocks.attempts_over_week', count: email_domain_block.history.reduce(0) { |sum, day| sum + day.accounts }) = t('admin.email_domain_blocks.attempts_over_week', count: email_domain_block.history.reduce(0) { |sum, day| sum + day.accounts })
- if email_domain_block.allow_with_approval?
·
= t('admin.email_domain_blocks.allow_registrations_with_approval')

View file

@ -7,6 +7,9 @@
.fields-group .fields-group
= f.input :domain, wrapper: :with_block_label, label: t('admin.email_domain_blocks.domain'), input_html: { readonly: defined?(@resolved_records) } = f.input :domain, wrapper: :with_block_label, label: t('admin.email_domain_blocks.domain'), input_html: { readonly: defined?(@resolved_records) }
.fields-group
= f.input :allow_with_approval, wrapper: :with_label, hint: false, label: I18n.t('admin.email_domain_blocks.allow_registrations_with_approval')
- if defined?(@resolved_records) - if defined?(@resolved_records)
%p.hint= t('admin.email_domain_blocks.resolved_dns_records_hint_html') %p.hint= t('admin.email_domain_blocks.resolved_dns_records_hint_html')

View file

@ -23,7 +23,7 @@ Rails.application.configure do
if Rails.env.production? if Rails.env.production?
"ws#{https ? 's' : ''}://#{web_host}" "ws#{https ? 's' : ''}://#{web_host}"
else else
"ws://#{ENV['REMOTE_DEV'] == 'true' ? host.split(':').first : 'localhost'}:4000" "ws://#{host.split(':').first}:4000"
end end
end end

View file

@ -46,6 +46,10 @@ ie:
title: 2FA desvalidat title: 2FA desvalidat
two_factor_enabled: two_factor_enabled:
title: 2FA permisset title: 2FA permisset
two_factor_recovery_codes_changed:
explanation: Li anteyan codes de recuperation ha esset ínvalidat, e novis generat.
subject: 'Mastodon: 2-factor codes de recuperation regenerat'
title: 2FA codes de recuperation changeat
unlock_instructions: unlock_instructions:
subject: 'Mastodon: Desserral instructiones' subject: 'Mastodon: Desserral instructiones'
webauthn_credential: webauthn_credential:
@ -55,8 +59,11 @@ ie:
webauthn_disabled: webauthn_disabled:
subject: 'Mastodon: Autentication con claves de securitá desactivisat' subject: 'Mastodon: Autentication con claves de securitá desactivisat'
title: Claves de securitá desactivisat title: Claves de securitá desactivisat
webauthn_enabled:
title: Claves de securitá activisat
omniauth_callbacks: omniauth_callbacks:
failure: Ne posset autenticar te de %{kind} pro "%{reason}". failure: Ne posset autenticar te de %{kind} pro "%{reason}".
success: Successosimen autenticat de conto %{kind}.
passwords: passwords:
no_token: Tu ne posse accessar ti-ci págine sin venir de un email pri reiniciar li passa-parol. Si tu ha venit de un email pri reiniciar li passa-parol, ples far cert que tu usat li complet URL providet. no_token: Tu ne posse accessar ti-ci págine sin venir de un email pri reiniciar li passa-parol. Si tu ha venit de un email pri reiniciar li passa-parol, ples far cert que tu usat li complet URL providet.
send_instructions: Si tui email-adresse existe in nor database, tu va reciver un ligament por recuperar li passa-parol a tui email-adresse in quelc minutes. Ples vider tui spam-emails si tu ne recivet ti email. send_instructions: Si tui email-adresse existe in nor database, tu va reciver un ligament por recuperar li passa-parol a tui email-adresse in quelc minutes. Ples vider tui spam-emails si tu ne recivet ti email.

View file

@ -31,6 +31,7 @@ ie:
redirect_uri: Usar un linea per URI redirect_uri: Usar un linea per URI
index: index:
application: Aplication application: Aplication
callback_url: URL de retrovocada
delete: Deleter delete: Deleter
empty: Tu have null aplicationes. empty: Tu have null aplicationes.
name: Nómine name: Nómine
@ -42,6 +43,7 @@ ie:
show: show:
actions: Actiones actions: Actiones
application_id: Clave de client application_id: Clave de client
callback_urls: URLs de retrovocada
secret: Secrete de client secret: Secrete de client
title: 'Aplication: %{name}' title: 'Aplication: %{name}'
authorizations: authorizations:
@ -51,6 +53,7 @@ ie:
error: error:
title: Alquo ha errat title: Alquo ha errat
new: new:
review_permissions: Inspecter permissiones
title: Autorisation besonat title: Autorisation besonat
authorized_applications: authorized_applications:
buttons: buttons:

View file

@ -472,6 +472,7 @@ en:
view: View domain block view: View domain block
email_domain_blocks: email_domain_blocks:
add_new: Add new add_new: Add new
allow_registrations_with_approval: Allow registrations with approval
attempts_over_week: attempts_over_week:
one: "%{count} attempt over the last week" one: "%{count} attempt over the last week"
other: "%{count} sign-up attempts over the last week" other: "%{count} sign-up attempts over the last week"

View file

@ -383,7 +383,11 @@ lad:
confirm_suspension: confirm_suspension:
cancel: Anula cancel: Anula
confirm: Suspende confirm: Suspende
permanent_action: Si kites la suspensyon no restoraras dingunos datos ni relasyones.
remove_all_data: Esto efasara todo el kontenido, multimedia i datos de profiles de los kuentos en este domeno de tu sirvidor.
stop_communication: Tu sirvidor deshara de komunikarse kon estos sirvidores.
title: Konfirma bloko de domeno para %{domain} title: Konfirma bloko de domeno para %{domain}
undo_relationships: Esto kitara todas las relasyones de segimyento entre tu kuentos en estos sirvidores i el tu sirvidor.
created_msg: El bloko de domeno esta siendo prosesado created_msg: El bloko de domeno esta siendo prosesado
destroyed_msg: El bloko de domeno se dezizo destroyed_msg: El bloko de domeno se dezizo
domain: Domeno domain: Domeno
@ -772,6 +776,8 @@ lad:
type: Tipo type: Tipo
types: types:
major: Versyon prinsipala major: Versyon prinsipala
minor: Versyon minora
patch: Versyon de remendo koreksyones de yerros i trokamientos simples
version: Versyon version: Versyon
statuses: statuses:
account: Autor account: Autor
@ -829,8 +835,10 @@ lad:
message_html: No ay dingun prosedura Sidekiq en egzekusion para la(s) kola(s) %{value}. Por favor, reviza tu konfigurasyon de Sidekiq message_html: No ay dingun prosedura Sidekiq en egzekusion para la(s) kola(s) %{value}. Por favor, reviza tu konfigurasyon de Sidekiq
software_version_critical_check: software_version_critical_check:
action: Amostra aktualizasyones desponivles action: Amostra aktualizasyones desponivles
message_html: Una aktualizasyon kritika de Mastodon esta desponivle. Por favor aktualiza pishin.
software_version_patch_check: software_version_patch_check:
action: Amostra aktualizasyones desponivles action: Amostra aktualizasyones desponivles
message_html: Una aktualizasyon de Mastodon kon koreksyon de yerros esta desponivle.
upload_check_privacy_error: upload_check_privacy_error:
action: Klika aki para mas enformasyon action: Klika aki para mas enformasyon
message_html: "<strong>Tu sirvidor de web es mal konfigurado. La privasita de tus utilizadores esta en riziko.</strong>" message_html: "<strong>Tu sirvidor de web es mal konfigurado. La privasita de tus utilizadores esta en riziko.</strong>"
@ -945,6 +953,7 @@ lad:
next_steps: Puedes achetar la apelasyon para dezazer la dechizyon de moderasyon, o ignorarla. next_steps: Puedes achetar la apelasyon para dezazer la dechizyon de moderasyon, o ignorarla.
subject: "%{username} esta apelando a una dechizyon de moderasyon en %{instance}" subject: "%{username} esta apelando a una dechizyon de moderasyon en %{instance}"
new_critical_software_updates: new_critical_software_updates:
body: Ay mueva versyon kritika de Mastodon. Es posivle ke keras aktualizar pishin!
subject: Ay aktualizasyones kritikas de Mastodon desponivles para %{instance}! subject: Ay aktualizasyones kritikas de Mastodon desponivles para %{instance}!
new_pending_account: new_pending_account:
body: Los peratim del muevo kuento estan abashos. Puedes achetar o refuzar esta aplikasyon. body: Los peratim del muevo kuento estan abashos. Puedes achetar o refuzar esta aplikasyon.
@ -1045,13 +1054,17 @@ lad:
accept: Acheta accept: Acheta
back: Atras back: Atras
preamble: Estas son establesidas i aplikadas por los moderadores de %{domain}. preamble: Estas son establesidas i aplikadas por los moderadores de %{domain}.
preamble_invited: Antes de kontinuar, por favor reviza las reglas del sirvidor establesidas por los moderatores de %{domain}.
title: Algunas reglas bazikas. title: Algunas reglas bazikas.
title_invited: Fuites envitado. title_invited: Fuites envitado.
security: Sigurita security: Sigurita
set_new_password: Establese muevo kod set_new_password: Establese muevo kod
setup: setup:
email_below_hint_html: Mira en tu kuti de spam o solisita de muevo. Si el adreso de posta elektronika ke aparese aki es yerrado, puedes trokarlo aki. email_below_hint_html: Mira en tu kuti de spam o solisita de muevo. Si el adreso de posta elektronika ke aparese aki es yerrado, puedes trokarlo aki.
email_settings_hint_html: Klika el atadjiko ke te embimos para verifikar %{email}. Asperaremos aki.
link_not_received: No risivites un atadijo? link_not_received: No risivites un atadijo?
new_confirmation_instructions_sent: Resiviras un muevo mesaj de posta elektronika kon el atadjio de konfirmasyon en unos minutos!
title: Reviza tu kuti de arivo
sign_in: sign_in:
preamble_html: Konektate kon tus kredensiales de <strong>%{domain}</strong>. Si tu kuento esta balabayado en otruno servidor, no puedras konektarte aki. preamble_html: Konektate kon tus kredensiales de <strong>%{domain}</strong>. Si tu kuento esta balabayado en otruno servidor, no puedras konektarte aki.
title: Konektate kon %{domain} title: Konektate kon %{domain}
@ -1246,9 +1259,11 @@ lad:
imports: imports:
errors: errors:
empty: Dosya CSV vaziya empty: Dosya CSV vaziya
incompatible_type: Inkompativle kon el tipo de importo eskojido
invalid_csv_file: 'Dosya CSV no valida. Yerro: %{error}' invalid_csv_file: 'Dosya CSV no valida. Yerro: %{error}'
over_rows_processing_limit: kontiene mas de %{count} filas over_rows_processing_limit: kontiene mas de %{count} filas
too_large: Dosya es mas grande too_large: Dosya es mas grande
failures: Yerros
imported: Importado imported: Importado
modes: modes:
merge: Une merge: Une
@ -1671,6 +1686,9 @@ lad:
month: "%b %Y" month: "%b %Y"
time: "%H:%M" time: "%H:%M"
with_time_zone: "%d de %b del %Y, %H:%M %Z" with_time_zone: "%d de %b del %Y, %H:%M %Z"
translation:
errors:
too_many_requests: Ay demaziadas solisitudes de servisyo de traduksyon.
two_factor_authentication: two_factor_authentication:
add: Adjusta add: Adjusta
disable: Inkapasita autentifikasyon en dos pasos disable: Inkapasita autentifikasyon en dos pasos
@ -1750,9 +1768,12 @@ lad:
title: Bienvenido, %{name}! title: Bienvenido, %{name}!
users: users:
follow_limit_reached: No puedes segir a mas de %{limit} personas follow_limit_reached: No puedes segir a mas de %{limit} personas
go_to_sso_account_settings: Va a la konfigurasyon de kuento de tu prokurador de identita
invalid_otp_token: Kodiche de dos pasos no valido invalid_otp_token: Kodiche de dos pasos no valido
otp_lost_help_html: Si pedriste akseso a los dos, puedes kontaktarte kon %{email}
signed_in_as: 'Konektado komo:' signed_in_as: 'Konektado komo:'
verification: verification:
here_is_how: Ansina es komo
verification: Verifikasyon verification: Verifikasyon
verified_links: Tus atadijos verifikados verified_links: Tus atadijos verifikados
webauthn_credentials: webauthn_credentials:

View file

@ -86,6 +86,7 @@ ie:
ip_block: ip_block:
comment: Facultativ. Ne obliviar pro quo tu adjuntet ti-ci regul. comment: Facultativ. Ne obliviar pro quo tu adjuntet ti-ci regul.
expires_in: IP-adresses es un ressurse finit, quelcvez partit e transferet de manu a manu. Pro to, un índefinit bloccada de IP ne es recomandat. expires_in: IP-adresses es un ressurse finit, quelcvez partit e transferet de manu a manu. Pro to, un índefinit bloccada de IP ne es recomandat.
ip: Intrar un adresse IPv4 o IPv6. Tu posse bloccar un tot intervalle de ili con li sintaxe CIDR. Atention a ne bloccar te self!
severities: severities:
no_access: Bloccar accesse a omni ressurses no_access: Bloccar accesse a omni ressurses
sign_up_block: Nov registrationes ne va esser possibil sign_up_block: Nov registrationes ne va esser possibil
@ -93,6 +94,10 @@ ie:
severity: Selecter quo va evenir con demandes ex ti-ci IP severity: Selecter quo va evenir con demandes ex ti-ci IP
rule: rule:
text: Descrir un regul o postulation por usatores sur ti-ci servitor. Prova scrir un descrition curt e simplic text: Descrir un regul o postulation por usatores sur ti-ci servitor. Prova scrir un descrition curt e simplic
sessions:
otp: 'Intrar li 2-factor code generat del app sur tui portabile o usar un de tui codes de recuperation:'
settings:
show_application: Totvez, tu va sempre posser vider quel app ha publicat tui posta.
user: user:
role: Permissiones de usator decidet per su rol role: Permissiones de usator decidet per su rol
user_role: user_role:
@ -111,6 +116,7 @@ ie:
name: Etiquette name: Etiquette
value: Contenete value: Contenete
indexable: Includer public postas in resultates de sercha indexable: Includer public postas in resultates de sercha
unlocked: Automaticmen acceptar nov sequitores
account_alias: account_alias:
acct: Usator-nómine del anteyan conto acct: Usator-nómine del anteyan conto
account_migration: account_migration:
@ -158,6 +164,7 @@ ie:
max_uses: Max grand númere de usas max_uses: Max grand númere de usas
new_password: Nov passa-parol new_password: Nov passa-parol
note: Biografie note: Biografie
otp_attempt: 2-factor code
password: Passa-parol password: Passa-parol
phrase: Clave-parol o frase phrase: Clave-parol o frase
setting_advanced_layout: Possibilisar web-interfacie avansat setting_advanced_layout: Possibilisar web-interfacie avansat
@ -165,10 +172,12 @@ ie:
setting_default_language: Lingue in quel postar setting_default_language: Lingue in quel postar
setting_default_privacy: Privatie de postada setting_default_privacy: Privatie de postada
setting_default_sensitive: Sempre marcar medie quam sensitiv setting_default_sensitive: Sempre marcar medie quam sensitiv
setting_display_media: Exposition de medie
setting_display_media_default: Predefinitiones setting_display_media_default: Predefinitiones
setting_display_media_hide_all: Celar omno setting_display_media_hide_all: Celar omno
setting_display_media_show_all: Monstrar omno setting_display_media_show_all: Monstrar omno
setting_expand_spoilers: Sempre expander postas marcat con admonitiones de contenete setting_expand_spoilers: Sempre expander postas marcat con admonitiones de contenete
setting_hide_network: Celar tui grafica social
setting_system_font_ui: Usar predefinit fonte de sistema setting_system_font_ui: Usar predefinit fonte de sistema
setting_theme: Tema de situ setting_theme: Tema de situ
setting_trends: Monstrar li hodial tendenties setting_trends: Monstrar li hodial tendenties
@ -179,6 +188,7 @@ ie:
title: Titul title: Titul
type: Specie de importation type: Specie de importation
username: Nómine de usator username: Nómine de usator
username_or_email: Usator-nómine o E-posta
whole_word: Plen parol whole_word: Plen parol
featured_tag: featured_tag:
name: Hashtag name: Hashtag
@ -193,11 +203,13 @@ ie:
custom_css: Custom CSS custom_css: Custom CSS
profile_directory: Possibilisar profilarium profile_directory: Possibilisar profilarium
registrations_mode: Qui posse registrar se registrations_mode: Qui posse registrar se
require_invite_text: Exiger un rason por adherer se
show_domain_blocks: Vider bloccas de dominia show_domain_blocks: Vider bloccas de dominia
show_domain_blocks_rationale: Monstrar pro quo cert dominias esset bloccat show_domain_blocks_rationale: Monstrar pro quo cert dominias esset bloccat
site_contact_email: Contact e-mail adresse site_contact_email: Contact e-mail adresse
site_contact_username: Usator-nómine de contact site_contact_username: Usator-nómine de contact
site_extended_description: Extendet descrition site_extended_description: Extendet descrition
site_short_description: Descrition del servitor
site_title: Nómine de servitor site_title: Nómine de servitor
theme: Predefenit tema theme: Predefenit tema
trendable_by_default: Possibilisar tendenties sin priori inspection trendable_by_default: Possibilisar tendenties sin priori inspection

View file

@ -22,11 +22,9 @@ class TruncatePreviewCards < ActiveRecord::Migration[5.1]
end end
def down def down
if ActiveRecord::Base.connection.table_exists? 'deprecated_preview_cards' raise ActiveRecord::IrreversibleMigration, 'Previous preview cards table has already been removed' unless ActiveRecord::Base.connection.table_exists? 'deprecated_preview_cards'
drop_table :preview_cards drop_table :preview_cards
rename_table :deprecated_preview_cards, :preview_cards rename_table :deprecated_preview_cards, :preview_cards
else
raise ActiveRecord::IrreversibleMigration, 'Previous preview cards table has already been removed'
end
end end
end end

View file

@ -105,7 +105,7 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
end end
end end
notifications_about_direct_statuses.includes(:account, mention: { status: [:account, mentions: :account] }).find_each do |notification| notifications_about_direct_statuses.includes(:account, mention: { status: [:account, { mentions: :account }] }).find_each do |notification|
MigrationAccountConversation.add_status(notification.account, notification.target_status) MigrationAccountConversation.add_status(notification.account, notification.target_status)
migrated += 1 migrated += 1

View file

@ -9,7 +9,7 @@ class PreserveOldLayoutForExistingUsers < ActiveRecord::Migration[5.2]
# on the to-be-changed default # on the to-be-changed default
User.where(User.arel_table[:current_sign_in_at].gteq(1.month.ago)).find_each do |user| User.where(User.arel_table[:current_sign_in_at].gteq(1.month.ago)).find_each do |user|
next if Setting.unscoped.where(thing_type: 'User', thing_id: user.id, var: 'advanced_layout').exists? next if Setting.unscoped.exists?(thing_type: 'User', thing_id: user.id, var: 'advanced_layout')
user.settings.advanced_layout = true user.settings.advanced_layout = true
end end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddAllowWithApprovalToEmailDomainBlocks < ActiveRecord::Migration[7.1]
def change
add_column :email_domain_blocks, :allow_with_approval, :boolean, default: false, null: false
end
end

View file

@ -6,36 +6,55 @@ class MigrateSettingsToUserRoles < ActiveRecord::Migration[6.1]
class UserRole < ApplicationRecord; end class UserRole < ApplicationRecord; end
def up def up
owner_role = UserRole.find_by(name: 'Owner') process_role_everyone
admin_role = UserRole.find_by(name: 'Admin') process_role_owner
moderator_role = UserRole.find_by(name: 'Moderator') process_role_admin
process_role_moderator
end
def down; end
private
def process_role_everyone
everyone_role = UserRole.find_by(id: -99) everyone_role = UserRole.find_by(id: -99)
return unless everyone_role
min_invite_role = Setting.min_invite_role
show_staff_badge = Setting.show_staff_badge
if everyone_role
everyone_role.permissions &= ~::UserRole::FLAGS[:invite_users] unless min_invite_role == 'user' everyone_role.permissions &= ~::UserRole::FLAGS[:invite_users] unless min_invite_role == 'user'
everyone_role.save everyone_role.save
end end
if owner_role def process_role_owner
owner_role = UserRole.find_by(name: 'Owner')
return unless owner_role
owner_role.highlighted = show_staff_badge owner_role.highlighted = show_staff_badge
owner_role.save owner_role.save
end end
if admin_role def process_role_admin
admin_role = UserRole.find_by(name: 'Admin')
return unless admin_role
admin_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(admin moderator).include?(min_invite_role) admin_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(admin moderator).include?(min_invite_role)
admin_role.highlighted = show_staff_badge admin_role.highlighted = show_staff_badge
admin_role.save admin_role.save
end end
if moderator_role def process_role_moderator
moderator_role = UserRole.find_by(name: 'Moderator')
return unless moderator_role
moderator_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(moderator).include?(min_invite_role) moderator_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(moderator).include?(min_invite_role)
moderator_role.highlighted = show_staff_badge moderator_role.highlighted = show_staff_badge
moderator_role.save moderator_role.save
end end
def min_invite_role
Setting.min_invite_role
end end
def down; end def show_staff_badge
Setting.show_staff_badge
end
end end

View file

@ -77,82 +77,130 @@ class BackfillAdminActionLogs < ActiveRecord::Migration[6.1]
def up def up
safety_assured do safety_assured do
process_logs_for_account
process_logs_for_user
process_logs_for_report
process_logs_for_domain_block
process_logs_for_domain_allow
process_logs_for_email_domain_block
process_logs_for_unavailable_domain
process_logs_for_status
process_logs_for_account_warning
process_logs_for_announcement
process_logs_for_ip_block
process_logs_for_custom_emoji
process_logs_for_canonical_email_block
process_logs_for_appeal
end
end
def down; end
private
def process_logs_for_account
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log| AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
next if log.account.nil? next if log.account.nil?
log.update_attribute('human_identifier', log.account.acct) log.update_attribute('human_identifier', log.account.acct)
end end
end
def process_logs_for_user
AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log| AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
next if log.user.nil? next if log.user.nil?
log.update_attribute('human_identifier', log.user.account.acct) log.update_attribute('human_identifier', log.user.account.acct)
log.update_attribute('route_param', log.user.account_id) log.update_attribute('route_param', log.user.account_id)
end end
end
def process_logs_for_report
AdminActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text') AdminActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
end
def process_logs_for_domain_block
AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log| AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
next if log.domain_block.nil? next if log.domain_block.nil?
log.update_attribute('human_identifier', log.domain_block.domain) log.update_attribute('human_identifier', log.domain_block.domain)
end end
end
def process_logs_for_domain_allow
AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log| AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
next if log.domain_allow.nil? next if log.domain_allow.nil?
log.update_attribute('human_identifier', log.domain_allow.domain) log.update_attribute('human_identifier', log.domain_allow.domain)
end end
end
def process_logs_for_email_domain_block
AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log| AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
next if log.email_domain_block.nil? next if log.email_domain_block.nil?
log.update_attribute('human_identifier', log.email_domain_block.domain) log.update_attribute('human_identifier', log.email_domain_block.domain)
end end
end
def process_logs_for_unavailable_domain
AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log| AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
next if log.unavailable_domain.nil? next if log.unavailable_domain.nil?
log.update_attribute('human_identifier', log.unavailable_domain.domain) log.update_attribute('human_identifier', log.unavailable_domain.domain)
end end
end
def process_logs_for_status
AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log| AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
next if log.status.nil? next if log.status.nil?
log.update_attribute('human_identifier', log.status.account.acct) log.update_attribute('human_identifier', log.status.account.acct)
log.update_attribute('permalink', log.status.uri) log.update_attribute('permalink', log.status.uri)
end end
end
def process_logs_for_account_warning
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log| AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
next if log.account_warning.nil? next if log.account_warning.nil?
log.update_attribute('human_identifier', log.account_warning.account.acct) log.update_attribute('human_identifier', log.account_warning.account.acct)
end end
end
def process_logs_for_announcement
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log| AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
next if log.announcement.nil? next if log.announcement.nil?
log.update_attribute('human_identifier', log.announcement.text) log.update_attribute('human_identifier', log.announcement.text)
end end
end
def process_logs_for_ip_block
AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log| AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
next if log.ip_block.nil? next if log.ip_block.nil?
log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}") log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
end end
end
def process_logs_for_custom_emoji
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log| AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
next if log.custom_emoji.nil? next if log.custom_emoji.nil?
log.update_attribute('human_identifier', log.custom_emoji.shortcode) log.update_attribute('human_identifier', log.custom_emoji.shortcode)
end end
end
def process_logs_for_canonical_email_block
AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log| AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
next if log.canonical_email_block.nil? next if log.canonical_email_block.nil?
log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash) log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash)
end end
end
def process_logs_for_appeal
AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log| AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
next if log.appeal.nil? next if log.appeal.nil?
@ -160,7 +208,4 @@ class BackfillAdminActionLogs < ActiveRecord::Migration[6.1]
log.update_attribute('route_param', log.appeal.account_warning_id) log.update_attribute('route_param', log.appeal.account_warning_id)
end end
end end
end
def down; end
end end

View file

@ -77,82 +77,130 @@ class BackfillAdminActionLogsAgain < ActiveRecord::Migration[6.1]
def up def up
safety_assured do safety_assured do
process_logs_for_account
process_logs_for_user
process_logs_for_report
process_logs_for_domain_block
process_logs_for_domain_allow
process_logs_for_email_domain_block
process_logs_for_unavailable_domain
process_logs_for_status
process_logs_for_account_warning
process_logs_for_announcement
process_logs_for_ip_block
process_logs_for_custom_emoji
process_logs_for_canonical_email_block
process_logs_for_appeal
end
end
def down; end
private
def process_logs_for_account
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log| AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
next if log.account.nil? next if log.account.nil?
log.update_attribute('human_identifier', log.account.acct) log.update_attribute('human_identifier', log.account.acct)
end end
end
def process_logs_for_user
AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log| AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
next if log.user.nil? next if log.user.nil?
log.update_attribute('human_identifier', log.user.account.acct) log.update_attribute('human_identifier', log.user.account.acct)
log.update_attribute('route_param', log.user.account_id) log.update_attribute('route_param', log.user.account_id)
end end
end
def process_logs_for_report
AdminActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text') AdminActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
end
def process_logs_for_domain_block
AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log| AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
next if log.domain_block.nil? next if log.domain_block.nil?
log.update_attribute('human_identifier', log.domain_block.domain) log.update_attribute('human_identifier', log.domain_block.domain)
end end
end
def process_logs_for_domain_allow
AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log| AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
next if log.domain_allow.nil? next if log.domain_allow.nil?
log.update_attribute('human_identifier', log.domain_allow.domain) log.update_attribute('human_identifier', log.domain_allow.domain)
end end
end
def process_logs_for_email_domain_block
AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log| AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
next if log.email_domain_block.nil? next if log.email_domain_block.nil?
log.update_attribute('human_identifier', log.email_domain_block.domain) log.update_attribute('human_identifier', log.email_domain_block.domain)
end end
end
def process_logs_for_unavailable_domain
AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log| AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
next if log.unavailable_domain.nil? next if log.unavailable_domain.nil?
log.update_attribute('human_identifier', log.unavailable_domain.domain) log.update_attribute('human_identifier', log.unavailable_domain.domain)
end end
end
def process_logs_for_status
AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log| AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
next if log.status.nil? next if log.status.nil?
log.update_attribute('human_identifier', log.status.account.acct) log.update_attribute('human_identifier', log.status.account.acct)
log.update_attribute('permalink', log.status.uri) log.update_attribute('permalink', log.status.uri)
end end
end
def process_logs_for_account_warning
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log| AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
next if log.account_warning.nil? next if log.account_warning.nil?
log.update_attribute('human_identifier', log.account_warning.account.acct) log.update_attribute('human_identifier', log.account_warning.account.acct)
end end
end
def process_logs_for_announcement
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log| AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
next if log.announcement.nil? next if log.announcement.nil?
log.update_attribute('human_identifier', log.announcement.text) log.update_attribute('human_identifier', log.announcement.text)
end end
end
def process_logs_for_ip_block
AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log| AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
next if log.ip_block.nil? next if log.ip_block.nil?
log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}") log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
end end
end
def process_logs_for_custom_emoji
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log| AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
next if log.custom_emoji.nil? next if log.custom_emoji.nil?
log.update_attribute('human_identifier', log.custom_emoji.shortcode) log.update_attribute('human_identifier', log.custom_emoji.shortcode)
end end
end
def process_logs_for_canonical_email_block
AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log| AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
next if log.canonical_email_block.nil? next if log.canonical_email_block.nil?
log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash) log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash)
end end
end
def process_logs_for_appeal
AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log| AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
next if log.appeal.nil? next if log.appeal.nil?
@ -160,7 +208,4 @@ class BackfillAdminActionLogsAgain < ActiveRecord::Migration[6.1]
log.update_attribute('route_param', log.appeal.account_warning_id) log.update_attribute('route_param', log.appeal.account_warning_id)
end end
end end
end
def down; end
end end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2023_12_14_225249) do ActiveRecord::Schema[7.1].define(version: 2023_12_22_100226) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -596,6 +596,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_12_14_225249) do
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.bigint "parent_id" t.bigint "parent_id"
t.boolean "allow_with_approval", default: false, null: false
t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true
end end

View file

@ -4,6 +4,7 @@ require_relative '../../../config/boot'
require_relative '../../../config/environment' require_relative '../../../config/environment'
require 'thor' require 'thor'
require 'pastel'
require_relative 'progress_helper' require_relative 'progress_helper'
module Mastodon module Mastodon

View file

@ -1,7 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'tty-prompt'
module Mastodon::CLI module Mastodon::CLI
module Federation module Federation
extend ActiveSupport::Concern extend ActiveSupport::Concern
@ -30,44 +28,50 @@ module Mastodon::CLI
LONG_DESC LONG_DESC
def self_destruct def self_destruct
if SelfDestructHelper.self_destruct? if SelfDestructHelper.self_destruct?
prompt.ok('Self-destruct mode is already enabled for this Mastodon server') say('Self-destruct mode is already enabled for this Mastodon server', :green)
pending_accounts = Account.local.without_suspended.count + Account.local.suspended.joins(:deletion_request).count pending_accounts = Account.local.without_suspended.count + Account.local.suspended.joins(:deletion_request).count
sidekiq_stats = Sidekiq::Stats.new sidekiq_stats = Sidekiq::Stats.new
if pending_accounts.positive? if pending_accounts.positive?
prompt.warn("#{pending_accounts} accounts are still pending deletion.") say("#{pending_accounts} accounts are still pending deletion.", :yellow)
elsif sidekiq_stats.enqueued.positive? elsif sidekiq_stats.enqueued.positive?
prompt.warn('Deletion notices are still being processed') say('Deletion notices are still being processed', :yellow)
elsif sidekiq_stats.retry_size.positive? elsif sidekiq_stats.retry_size.positive?
prompt.warn('At least one delivery attempt for each deletion notice has been made, but some have failed and are scheduled for retry') say('At least one delivery attempt for each deletion notice has been made, but some have failed and are scheduled for retry', :yellow)
else else
prompt.ok('Every deletion notice has been sent! You can safely delete all data and decomission your servers!') say('Every deletion notice has been sent! You can safely delete all data and decomission your servers!', :green)
end end
exit(0) exit(0)
end end
exit(1) unless prompt.ask('Type in the domain of the server to confirm:', required: true) == Rails.configuration.x.local_domain exit(1) unless ask('Type in the domain of the server to confirm:') == Rails.configuration.x.local_domain
prompt.warn('This operation WILL NOT be reversible.') say(<<~WARNING, :yellow)
prompt.warn('While the data won\'t be erased locally, the server will be in a BROKEN STATE afterwards.') This operation WILL NOT be reversible.
prompt.warn('The deletion process itself may take a long time, and will be handled by Sidekiq, so do not shut it down until it has finished (you will be able to re-run this command to see the state of the self-destruct process).') While the data won't be erased locally, the server will be in a BROKEN STATE afterwards.
The deletion process itself may take a long time, and will be handled by Sidekiq, so do not shut it down until it has finished (you will be able to re-run this command to see the state of the self-destruct process).
WARNING
exit(1) if prompt.no?('Are you sure you want to proceed?') exit(1) if no?('Are you sure you want to proceed?')
self_destruct_value = Rails.application.message_verifier('self-destruct').generate(Rails.configuration.x.local_domain) say(<<~INSTRUCTIONS, :green)
prompt.ok('To switch Mastodon to self-destruct mode, add the following variable to your evironment (e.g. by adding a line to your `.env.production`) and restart all Mastodon processes:') To switch Mastodon to self-destruct mode, add the following variable to your evironment (e.g. by adding a line to your `.env.production`) and restart all Mastodon processes:
prompt.ok(" SELF_DESTRUCT=#{self_destruct_value}") SELF_DESTRUCT=#{self_destruct_value}
prompt.ok("\nYou can re-run this command to see the state of the self-destruct process.") You can re-run this command to see the state of the self-destruct process.
rescue TTY::Reader::InputInterrupt INSTRUCTIONS
rescue Interrupt
exit(1) exit(1)
end end
private private
def prompt def self_destruct_value
@prompt ||= TTY::Prompt.new Rails
.application
.message_verifier('self-destruct')
.generate(Rails.configuration.x.local_domain)
end end
end end
end end

View file

@ -1,6 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'tty-prompt'
require_relative 'base' require_relative 'base'
module Mastodon::CLI module Mastodon::CLI

View file

@ -207,7 +207,7 @@
"prettier": "^3.0.0", "prettier": "^3.0.0",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
"stylelint": "^16.0.2", "stylelint": "^16.0.2",
"stylelint-config-standard-scss": "^12.0.0", "stylelint-config-standard-scss": "^13.0.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"webpack-dev-server": "^3.11.3", "webpack-dev-server": "^3.11.3",
"yargs": "^17.7.2" "yargs": "^17.7.2"

View file

@ -12,13 +12,14 @@ RSpec.describe Admin::EmailDomainBlocksController do
describe 'GET #index' do describe 'GET #index' do
around do |example| around do |example|
default_per_page = EmailDomainBlock.default_per_page default_per_page = EmailDomainBlock.default_per_page
EmailDomainBlock.paginates_per 1 EmailDomainBlock.paginates_per 2
example.run example.run
EmailDomainBlock.paginates_per default_per_page EmailDomainBlock.paginates_per default_per_page
end end
it 'returns http success' do it 'returns http success' do
2.times { Fabricate(:email_domain_block) } 2.times { Fabricate(:email_domain_block) }
Fabricate(:email_domain_block, allow_with_approval: true)
get :index, params: { page: 2 } get :index, params: { page: 2 }
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end

View file

@ -135,6 +135,25 @@ RSpec.describe Auth::RegistrationsController do
end end
end end
context 'when user has an email address requiring approval' do
subject do
Setting.registrations_mode = 'open'
Fabricate(:email_domain_block, allow_with_approval: true, domain: 'example.com')
request.headers['Accept-Language'] = accept_language
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } }
end
it 'creates unapproved user and redirects to setup' do
subject
expect(response).to redirect_to auth_setup_path
user = User.find_by(email: 'test@example.com')
expect(user).to_not be_nil
expect(user.locale).to eq(accept_language)
expect(user.approved).to be(false)
end
end
context 'with Approval-based registrations without invite' do context 'with Approval-based registrations without invite' do
subject do subject do
Setting.registrations_mode = 'approved' Setting.registrations_mode = 'approved'

View file

@ -22,7 +22,7 @@ describe 'Admin::Accounts' do
context 'without selecting any accounts' do context 'without selecting any accounts' do
it 'displays a notice about account selection' do it 'displays a notice about account selection' do
click_button button_for_suspend click_on button_for_suspend
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end
@ -32,7 +32,7 @@ describe 'Admin::Accounts' do
it 'suspends the account' do it 'suspends the account' do
batch_checkbox_for(approved_user_account).check batch_checkbox_for(approved_user_account).check
click_button button_for_suspend click_on button_for_suspend
expect(approved_user_account.reload).to be_suspended expect(approved_user_account.reload).to be_suspended
end end
@ -42,7 +42,7 @@ describe 'Admin::Accounts' do
it 'approves the account user' do it 'approves the account user' do
batch_checkbox_for(unapproved_user_account).check batch_checkbox_for(unapproved_user_account).check
click_button button_for_approve click_on button_for_approve
expect(unapproved_user_account.reload.user).to be_approved expect(unapproved_user_account.reload.user).to be_approved
end end
@ -52,7 +52,7 @@ describe 'Admin::Accounts' do
it 'rejects and removes the account' do it 'rejects and removes the account' do
batch_checkbox_for(unapproved_user_account).check batch_checkbox_for(unapproved_user_account).check
click_button button_for_reject click_on button_for_reject
expect { unapproved_user_account.reload }.to raise_error(ActiveRecord::RecordNotFound) expect { unapproved_user_account.reload }.to raise_error(ActiveRecord::RecordNotFound)
end end

View file

@ -16,7 +16,7 @@ describe 'Admin::CustomEmojis' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_enable click_on button_for_enable
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -14,7 +14,7 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'example.com' fill_in 'domain_block_domain', with: 'example.com'
select I18n.t('admin.domain_blocks.new.severity.silence'), from: 'domain_block_severity' select I18n.t('admin.domain_blocks.new.severity.silence'), from: 'domain_block_severity'
click_button I18n.t('admin.domain_blocks.new.create') click_on I18n.t('admin.domain_blocks.new.create')
expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true
expect(DomainBlockWorker).to have_received(:perform_async) expect(DomainBlockWorker).to have_received(:perform_async)
@ -27,14 +27,14 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'example.com' fill_in 'domain_block_domain', with: 'example.com'
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
click_button I18n.t('admin.domain_blocks.new.create') click_on I18n.t('admin.domain_blocks.new.create')
# It doesn't immediately block but presents a confirmation screen # It doesn't immediately block but presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com')) expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
expect(DomainBlockWorker).to_not have_received(:perform_async) expect(DomainBlockWorker).to_not have_received(:perform_async)
# Confirming creates a block # Confirming creates a block
click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
expect(DomainBlockWorker).to have_received(:perform_async) expect(DomainBlockWorker).to have_received(:perform_async)
@ -49,14 +49,14 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'example.com' fill_in 'domain_block_domain', with: 'example.com'
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
click_button I18n.t('admin.domain_blocks.new.create') click_on I18n.t('admin.domain_blocks.new.create')
# It doesn't immediately block but presents a confirmation screen # It doesn't immediately block but presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com')) expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
expect(DomainBlockWorker).to_not have_received(:perform_async) expect(DomainBlockWorker).to_not have_received(:perform_async)
# Confirming updates the block # Confirming updates the block
click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(domain_block.reload.severity).to eq 'suspend' expect(domain_block.reload.severity).to eq 'suspend'
expect(DomainBlockWorker).to have_received(:perform_async) expect(DomainBlockWorker).to have_received(:perform_async)
@ -71,14 +71,14 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'subdomain.example.com' fill_in 'domain_block_domain', with: 'subdomain.example.com'
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
click_button I18n.t('admin.domain_blocks.new.create') click_on I18n.t('admin.domain_blocks.new.create')
# It doesn't immediately block but presents a confirmation screen # It doesn't immediately block but presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'subdomain.example.com')) expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'subdomain.example.com'))
expect(DomainBlockWorker).to_not have_received(:perform_async) expect(DomainBlockWorker).to_not have_received(:perform_async)
# Confirming creates the block # Confirming creates the block
click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(DomainBlock.where(domain: 'subdomain.example.com', severity: 'suspend')).to exist expect(DomainBlock.where(domain: 'subdomain.example.com', severity: 'suspend')).to exist
expect(DomainBlockWorker).to have_received(:perform_async) expect(DomainBlockWorker).to have_received(:perform_async)
@ -96,14 +96,14 @@ describe 'blocking domains through the moderation interface' do
visit edit_admin_domain_block_path(domain_block) visit edit_admin_domain_block_path(domain_block)
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
click_button I18n.t('generic.save_changes') click_on I18n.t('generic.save_changes')
# It doesn't immediately block but presents a confirmation screen # It doesn't immediately block but presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com')) expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
expect(DomainBlockWorker).to_not have_received(:perform_async) expect(DomainBlockWorker).to_not have_received(:perform_async)
# Confirming updates the block # Confirming updates the block
click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(DomainBlockWorker).to have_received(:perform_async) expect(DomainBlockWorker).to have_received(:perform_async)
expect(domain_block.reload.severity).to eq 'suspend' expect(domain_block.reload.severity).to eq 'suspend'

View file

@ -16,7 +16,7 @@ describe 'Admin::EmailDomainBlocks' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_delete click_on button_for_delete
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -16,7 +16,7 @@ describe 'Admin::IpBlocks' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_delete click_on button_for_delete
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -11,13 +11,13 @@ describe 'finding software updates through the admin interface' do
it 'shows a link to the software updates page, which links to release notes' do it 'shows a link to the software updates page, which links to release notes' do
visit settings_profile_path visit settings_profile_path
click_link I18n.t('admin.critical_update_pending') click_on I18n.t('admin.critical_update_pending')
expect(page).to have_title(I18n.t('admin.software_updates.title')) expect(page).to have_title(I18n.t('admin.software_updates.title'))
expect(page).to have_content('99.99.99') expect(page).to have_content('99.99.99')
click_link I18n.t('admin.software_updates.release_notes') click_on I18n.t('admin.software_updates.release_notes')
expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true) expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true)
end end
end end

View file

@ -17,7 +17,7 @@ describe 'Admin::Statuses' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_report click_on button_for_report
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Links::PreviewCardProviders' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_allow click_on button_for_allow
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Links' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_allow click_on button_for_allow
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Statuses' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_allow click_on button_for_allow
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Tags' do
context 'without selecting any records' do context 'without selecting any records' do
it 'displays a notice about selection' do it 'displays a notice about selection' do
click_button button_for_allow click_on button_for_allow
expect(page).to have_content(selection_error_text) expect(page).to have_content(selection_error_text)
end end

View file

@ -23,7 +23,7 @@ describe 'email confirmation flow when captcha is enabled' do
expect(user.reload.confirmed?).to be false expect(user.reload.confirmed?).to be false
# It redirects to app and confirms user # It redirects to app and confirms user
click_button I18n.t('challenge.confirm') click_on I18n.t('challenge.confirm')
expect(user.reload.confirmed?).to be true expect(user.reload.confirmed?).to be true
expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true)

View file

@ -19,7 +19,7 @@ describe 'Log in' do
it 'A valid email and password user is able to log in' do it 'A valid email and password user is able to log in' do
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: password fill_in 'user_password', with: password
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(subject).to have_css('div.app-holder') expect(subject).to have_css('div.app-holder')
end end
@ -27,7 +27,7 @@ describe 'Log in' do
it 'A invalid email and password user is not able to log in' do it 'A invalid email and password user is not able to log in' do
fill_in 'user_email', with: 'invalid_email' fill_in 'user_email', with: 'invalid_email'
fill_in 'user_password', with: 'invalid_password' fill_in 'user_password', with: 'invalid_password'
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(subject).to have_css('.flash-message', text: failure_message('invalid')) expect(subject).to have_css('.flash-message', text: failure_message('invalid'))
end end
@ -38,7 +38,7 @@ describe 'Log in' do
it 'A unconfirmed user is able to log in' do it 'A unconfirmed user is able to log in' do
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: password fill_in 'user_password', with: password
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(subject).to have_css('div.admin-wrapper') expect(subject).to have_css('div.admin-wrapper')
end end

View file

@ -20,7 +20,7 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL # Upon authorizing, it redirects to the apps' callback URL
click_button I18n.t('doorkeeper.authorizations.buttons.authorize') click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It grants the app access to the account # It grants the app access to the account
@ -35,7 +35,7 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.deny')) expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.deny'))
# Upon denying, it redirects to the apps' callback URL # Upon denying, it redirects to the apps' callback URL
click_button I18n.t('doorkeeper.authorizations.buttons.deny') click_on I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It does not grant the app access to the account # It does not grant the app access to the account
@ -63,17 +63,17 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again # Failing to log-in presents the form again
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password' fill_in 'user_password', with: 'wrong password'
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login')) expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to an authorization page # Logging in redirects to an authorization page
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: password fill_in 'user_password', with: password
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL # Upon authorizing, it redirects to the apps' callback URL
click_button I18n.t('doorkeeper.authorizations.buttons.authorize') click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It grants the app access to the account # It grants the app access to the account
@ -90,17 +90,17 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again # Failing to log-in presents the form again
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password' fill_in 'user_password', with: 'wrong password'
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login')) expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to an authorization page # Logging in redirects to an authorization page
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: password fill_in 'user_password', with: password
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL # Upon denying, it redirects to the apps' callback URL
click_button I18n.t('doorkeeper.authorizations.buttons.deny') click_on I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It does not grant the app access to the account # It does not grant the app access to the account
@ -120,27 +120,27 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again # Failing to log-in presents the form again
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password' fill_in 'user_password', with: 'wrong password'
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login')) expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to a two-factor authentication page # Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: password fill_in 'user_password', with: password
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in an incorrect two-factor authentication code presents the form again # Filling in an incorrect two-factor authentication code presents the form again
fill_in 'user_otp_attempt', with: 'wrong' fill_in 'user_otp_attempt', with: 'wrong'
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in the correct TOTP code redirects to an app authorization page # Filling in the correct TOTP code redirects to an app authorization page
fill_in 'user_otp_attempt', with: user.current_otp fill_in 'user_otp_attempt', with: user.current_otp
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL # Upon authorizing, it redirects to the apps' callback URL
click_button I18n.t('doorkeeper.authorizations.buttons.authorize') click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It grants the app access to the account # It grants the app access to the account
@ -157,27 +157,27 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again # Failing to log-in presents the form again
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password' fill_in 'user_password', with: 'wrong password'
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login')) expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to a two-factor authentication page # Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: password fill_in 'user_password', with: password
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in an incorrect two-factor authentication code presents the form again # Filling in an incorrect two-factor authentication code presents the form again
fill_in 'user_otp_attempt', with: 'wrong' fill_in 'user_otp_attempt', with: 'wrong'
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in the correct TOTP code redirects to an app authorization page # Filling in the correct TOTP code redirects to an app authorization page
fill_in 'user_otp_attempt', with: user.current_otp fill_in 'user_otp_attempt', with: user.current_otp
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL # Upon denying, it redirects to the apps' callback URL
click_button I18n.t('doorkeeper.authorizations.buttons.deny') click_on I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It does not grant the app access to the account # It does not grant the app access to the account

View file

@ -20,4 +20,157 @@ describe Mastodon::CLI::Main do
.to output_results(Mastodon::Version.to_s) .to output_results(Mastodon::Version.to_s)
end end
end end
describe '#self_destruct' do
let(:action) { :self_destruct }
context 'with self destruct mode enabled' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(true)
end
context 'with pending accounts' do
before { Fabricate(:account) }
it 'reports about pending accounts' do
expect { subject }
.to output_results(
'already enabled',
'still pending deletion'
)
.and raise_error(SystemExit)
end
end
context 'with sidekiq notices being processed' do
before do
Account.delete_all
stats_double = instance_double(Sidekiq::Stats, enqueued: 5)
allow(Sidekiq::Stats).to receive(:new).and_return(stats_double)
end
it 'reports about notices' do
expect { subject }
.to output_results(
'already enabled',
'notices are still being'
)
.and raise_error(SystemExit)
end
end
context 'with sidekiq failed deliveries' do
before do
Account.delete_all
stats_double = instance_double(Sidekiq::Stats, enqueued: 0, retry_size: 10)
allow(Sidekiq::Stats).to receive(:new).and_return(stats_double)
end
it 'reports about notices' do
expect { subject }
.to output_results(
'already enabled',
'some have failed and are scheduled'
)
.and raise_error(SystemExit)
end
end
context 'with self descruct mode ready' do
before do
Account.delete_all
stats_double = instance_double(Sidekiq::Stats, enqueued: 0, retry_size: 0)
allow(Sidekiq::Stats).to receive(:new).and_return(stats_double)
end
it 'reports about notices' do
expect { subject }
.to output_results(
'already enabled',
'can safely delete all data'
)
.and raise_error(SystemExit)
end
end
end
context 'with self destruct mode disabled' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(false)
end
context 'with an incorrect response to hostname' do
before do
answer_hostname_incorrectly
end
it 'exits silently' do
expect { subject }
.to raise_error(SystemExit)
end
end
context 'with a correct response to hostname but no to proceed' do
before do
answer_hostname_correctly
decline_proceed
end
it 'passes first step but stops before instructions' do
expect { subject }
.to output_results('operation WILL NOT')
.and raise_error(SystemExit)
end
end
context 'with a correct response to hostname and yes to proceed' do
before do
answer_hostname_correctly
accept_proceed
end
it 'instructs to set the appropriate environment variable' do
expect { subject }
.to output_results(
'operation WILL NOT',
'the following variable'
)
end
end
private
def answer_hostname_incorrectly
allow(cli.shell)
.to receive(:ask)
.with('Type in the domain of the server to confirm:')
.and_return('wrong.host')
.once
end
def answer_hostname_correctly
allow(cli.shell)
.to receive(:ask)
.with('Type in the domain of the server to confirm:')
.and_return(Rails.configuration.x.local_domain)
.once
end
def decline_proceed
allow(cli.shell)
.to receive(:no?)
.with('Are you sure you want to proceed?')
.and_return(true)
.once
end
def accept_proceed
allow(cli.shell)
.to receive(:no?)
.with('Are you sure you want to proceed?')
.and_return(false)
.once
end
end
end
end end

View file

@ -184,4 +184,58 @@ describe Mastodon::CLI::Media do
end end
end end
end end
describe '#remove_orphans' do
let(:action) { :remove_orphans }
before do
FileUtils.mkdir_p Rails.public_path.join('system')
end
context 'without any options' do
it 'runs without error' do
expect { subject }
.to output_results('Removed', 'orphans (approx')
end
end
context 'when in azure mode' do
before do
allow(Paperclip::Attachment).to receive(:default_options).and_return(storage: :azure)
end
it 'warns about usage and exits' do
expect { subject }
.to output_results('azure storage driver is not supported')
.and raise_error(SystemExit)
end
end
context 'when in fog mode' do
before do
allow(Paperclip::Attachment).to receive(:default_options).and_return(storage: :fog)
end
it 'warns about usage and exits' do
expect { subject }
.to output_results('fog storage driver is not supported')
.and raise_error(SystemExit)
end
end
context 'when in filesystem mode' do
before do
allow(File).to receive(:delete).and_return(true)
media_attachment.delete
end
let(:media_attachment) { Fabricate(:media_attachment) }
it 'removes the unlinked files' do
expect { subject }
.to output_results('Removed', 'orphans (approx')
expect(File).to have_received(:delete).with(media_attachment.file.path)
end
end
end
end end

View file

@ -33,11 +33,13 @@ describe RequestPool do
subject subject
threads = Array.new(20) do |_i| threads = Array.new(3) do
Thread.new do Thread.new do
20.times do 2.times do
subject.with('http://example.com') do |http_client| subject.with('http://example.com') do |http_client|
http_client.get('/').flush http_client.get('/').flush
# Nudge scheduler to yield and exercise the full pool
sleep(0)
end end
end end
end end

View file

@ -20,7 +20,7 @@ describe 'Content-Security-Policy' do
"form-action 'self'", "form-action 'self'",
"child-src 'self' blob: https://cb6e6126.ngrok.io", "child-src 'self' blob: https://cb6e6126.ngrok.io",
"worker-src 'self' blob: https://cb6e6126.ngrok.io", "worker-src 'self' blob: https://cb6e6126.ngrok.io",
"connect-src 'self' data: blob: https://cb6e6126.ngrok.io ws://localhost:4000", "connect-src 'self' data: blob: https://cb6e6126.ngrok.io ws://cb6e6126.ngrok.io:4000",
"script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'" "script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'"
) )
end end

View file

@ -94,6 +94,72 @@ describe 'signature verification concern' do
end end
end end
context 'with a valid signature on a GET request that has a query string' do
let(:signature_header) do
'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="SDMa4r/DQYMXYxVgYO2yEqGWWUXugKjVuz0I8dniQAk+aunzBaF2aPu+4grBfawAshlx1Xytl8lhb0H2MllEz16/tKY7rUrb70MK0w8ohXgpb0qs3YvQgdj4X24L1x2MnkFfKHR/J+7TBlnivq0HZqXm8EIkPWLv+eQxu8fbowLwHIVvRd/3t6FzvcfsE0UZKkoMEX02542MhwSif6cu7Ec/clsY9qgKahb9JVGOGS1op9Lvg/9y1mc8KCgD83U5IxVygYeYXaVQ6gixA9NgZiTCwEWzHM5ELm7w5hpdLFYxYOHg/3G3fiqJzpzNQAcCD4S4JxfE7hMI0IzVlNLT6A=="' # rubocop:disable Layout/LineLength
end
it 'successfuly verifies signature', :aggregate_failures do
expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success?foo=42', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' })
get '/activitypub/success?foo=42', headers: {
'Host' => 'www.example.com',
'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
'Signature' => signature_header,
}
expect(response).to have_http_status(200)
expect(body_as_json).to match(
signed_request: true,
signature_actor_id: actor.id.to_s
)
end
end
context 'when the query string is missing from the signature verification (compatibility quirk)' do
let(:signature_header) do
'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="' # rubocop:disable Layout/LineLength
end
it 'successfuly verifies signature', :aggregate_failures do
expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' })
get '/activitypub/success?foo=42', headers: {
'Host' => 'www.example.com',
'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
'Signature' => signature_header,
}
expect(response).to have_http_status(200)
expect(body_as_json).to match(
signed_request: true,
signature_actor_id: actor.id.to_s
)
end
end
context 'with mismatching query string' do
let(:signature_header) do
'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="SDMa4r/DQYMXYxVgYO2yEqGWWUXugKjVuz0I8dniQAk+aunzBaF2aPu+4grBfawAshlx1Xytl8lhb0H2MllEz16/tKY7rUrb70MK0w8ohXgpb0qs3YvQgdj4X24L1x2MnkFfKHR/J+7TBlnivq0HZqXm8EIkPWLv+eQxu8fbowLwHIVvRd/3t6FzvcfsE0UZKkoMEX02542MhwSif6cu7Ec/clsY9qgKahb9JVGOGS1op9Lvg/9y1mc8KCgD83U5IxVygYeYXaVQ6gixA9NgZiTCwEWzHM5ELm7w5hpdLFYxYOHg/3G3fiqJzpzNQAcCD4S4JxfE7hMI0IzVlNLT6A=="' # rubocop:disable Layout/LineLength
end
it 'fails to verify signature', :aggregate_failures do
expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success?foo=42', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' })
get '/activitypub/success?foo=43', headers: {
'Host' => 'www.example.com',
'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
'Signature' => signature_header,
}
expect(body_as_json).to match(
signed_request: true,
signature_actor_id: nil,
error: anything
)
end
end
context 'with a mismatching path' do context 'with a mismatching path' do
it 'fails to verify signature', :aggregate_failures do it 'fails to verify signature', :aggregate_failures do
get '/activitypub/alternative-path', headers: { get '/activitypub/alternative-path', headers: {

View file

@ -27,6 +27,27 @@ RSpec.describe AppSignUpService, type: :service do
end end
end end
context 'when the email address requires approval' do
before do
Setting.registrations_mode = 'open'
Fabricate(:email_domain_block, allow_with_approval: true, domain: 'email.com')
end
it 'creates an unapproved user', :aggregate_failures do
access_token = subject.call(app, remote_ip, params)
expect(access_token).to_not be_nil
expect(access_token.scopes.to_s).to eq 'read write'
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
expect(user.confirmed?).to be false
expect(user.approved?).to be false
expect(user.account).to_not be_nil
expect(user.invite_request).to be_nil
end
end
context 'when registrations are closed' do context 'when registrations are closed' do
before do before do
Setting.registrations_mode = 'none' Setting.registrations_mode = 'none'

View file

@ -18,7 +18,7 @@ module ProfileStories
visit new_user_session_path visit new_user_session_path
fill_in 'user_email', with: email fill_in 'user_email', with: email
fill_in 'user_password', with: password fill_in 'user_password', with: password
click_button I18n.t('auth.login') click_on I18n.t('auth.login')
end end
def with_alice_as_local_user def with_alice_as_local_user

View file

@ -24,7 +24,7 @@ describe 'NewStatuses' do
within('.compose-form') do within('.compose-form') do
fill_in "What's on your mind?", with: status_text fill_in "What's on your mind?", with: status_text
click_button 'Publish!' click_on 'Publish!'
end end
expect(subject).to have_css('.status__content__text', text: status_text) expect(subject).to have_css('.status__content__text', text: status_text)
@ -37,7 +37,7 @@ describe 'NewStatuses' do
within('.compose-form') do within('.compose-form') do
fill_in "What's on your mind?", with: status_text fill_in "What's on your mind?", with: status_text
click_button 'Publish!' click_on 'Publish!'
end end
expect(subject).to have_css('.status__content__text', text: status_text) expect(subject).to have_css('.status__content__text', text: status_text)

View file

@ -5,6 +5,7 @@ const http = require('http');
const path = require('path'); const path = require('path');
const url = require('url'); const url = require('url');
const cors = require('cors');
const dotenv = require('dotenv'); const dotenv = require('dotenv');
const express = require('express'); const express = require('express');
const Redis = require('ioredis'); const Redis = require('ioredis');
@ -187,6 +188,7 @@ const startServer = async () => {
const pgPool = new pg.Pool(pgConfigFromEnv(process.env)); const pgPool = new pg.Pool(pgConfigFromEnv(process.env));
const server = http.createServer(app); const server = http.createServer(app);
app.use(cors());
/** /**
* @type {Object.<string, Array.<function(Object<string, any>): void>>} * @type {Object.<string, Array.<function(Object<string, any>): void>>}
@ -327,19 +329,6 @@ const startServer = async () => {
} }
}; };
/**
* @param {any} req
* @param {any} res
* @param {function(Error=): void} next
*/
const allowCrossDomain = (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Authorization, Accept, Cache-Control');
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
next();
};
/** /**
* @param {any} req * @param {any} req
* @param {any} res * @param {any} res
@ -1042,7 +1031,6 @@ const startServer = async () => {
api.use(setRequestId); api.use(setRequestId);
api.use(setRemoteAddress); api.use(setRemoteAddress);
api.use(allowCrossDomain);
api.use(authenticationMiddleware); api.use(authenticationMiddleware);
api.use(errorMiddleware); api.use(errorMiddleware);

View file

@ -16,6 +16,7 @@
"check:types": "tsc --noEmit" "check:types": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
@ -28,6 +29,7 @@
"ws": "^8.12.1" "ws": "^8.12.1"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.16",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/npmlog": "^7.0.0", "@types/npmlog": "^7.0.0",
"@types/pg": "^8.6.6", "@types/pg": "^8.6.6",

267
yarn.lock
View file

@ -2452,7 +2452,7 @@ __metadata:
stacktrace-js: "npm:^2.0.2" stacktrace-js: "npm:^2.0.2"
stringz: "npm:^2.1.0" stringz: "npm:^2.1.0"
stylelint: "npm:^16.0.2" stylelint: "npm:^16.0.2"
stylelint-config-standard-scss: "npm:^12.0.0" stylelint-config-standard-scss: "npm:^13.0.0"
substring-trie: "npm:^1.0.2" substring-trie: "npm:^1.0.2"
terser-webpack-plugin: "npm:^4.2.3" terser-webpack-plugin: "npm:^4.2.3"
tesseract.js: "npm:^2.1.5" tesseract.js: "npm:^2.1.5"
@ -2487,12 +2487,14 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@mastodon/streaming@workspace:streaming" resolution: "@mastodon/streaming@workspace:streaming"
dependencies: dependencies:
"@types/cors": "npm:^2.8.16"
"@types/express": "npm:^4.17.17" "@types/express": "npm:^4.17.17"
"@types/npmlog": "npm:^7.0.0" "@types/npmlog": "npm:^7.0.0"
"@types/pg": "npm:^8.6.6" "@types/pg": "npm:^8.6.6"
"@types/uuid": "npm:^9.0.0" "@types/uuid": "npm:^9.0.0"
"@types/ws": "npm:^8.5.9" "@types/ws": "npm:^8.5.9"
bufferutil: "npm:^4.0.7" bufferutil: "npm:^4.0.7"
cors: "npm:^2.8.5"
dotenv: "npm:^16.0.3" dotenv: "npm:^16.0.3"
eslint-define-config: "npm:^2.0.0" eslint-define-config: "npm:^2.0.0"
express: "npm:^4.18.2" express: "npm:^4.18.2"
@ -3046,6 +3048,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/cors@npm:^2.8.16":
version: 2.8.16
resolution: "@types/cors@npm:2.8.16"
dependencies:
"@types/node": "npm:*"
checksum: ebcfb325b102739249bbaa4845cf1cf4830baf5490a32bcd1a85cd9b8c4d4b9eaaaea94423e454b5b7c9da77e46a64db80d2381d3bc3f940d15d13814e87b70a
languageName: node
linkType: hard
"@types/emoji-mart@npm:^3.0.9": "@types/emoji-mart@npm:^3.0.9":
version: 3.0.14 version: 3.0.14
resolution: "@types/emoji-mart@npm:3.0.14" resolution: "@types/emoji-mart@npm:3.0.14"
@ -4697,13 +4708,13 @@ __metadata:
linkType: hard linkType: hard
"axios@npm:^1.4.0": "axios@npm:^1.4.0":
version: 1.6.3 version: 1.6.4
resolution: "axios@npm:1.6.3" resolution: "axios@npm:1.6.4"
dependencies: dependencies:
follow-redirects: "npm:^1.15.0" follow-redirects: "npm:^1.15.4"
form-data: "npm:^4.0.0" form-data: "npm:^4.0.0"
proxy-from-env: "npm:^1.1.0" proxy-from-env: "npm:^1.1.0"
checksum: dcc6d982353db33e6893ef01cdf81d0a0548dbd8fba0cb046dc4aee1a6a16226721faa4c2a13b2673d47130509629cdb93bb991b3a2bd4ef17a5ac27a8bba0da checksum: daac697fa1ea9865cb48e9edb7eacd99e8a9214997f2d8e886cb61c380a613e5c270078bfc153ac96206680106c223f005f0e4bf2f3b2ddd88e559ecf970521f
languageName: node languageName: node
linkType: hard linkType: hard
@ -5216,7 +5227,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2": "browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.22.2":
version: 4.22.2 version: 4.22.2
resolution: "browserslist@npm:4.22.2" resolution: "browserslist@npm:4.22.2"
dependencies: dependencies:
@ -5992,6 +6003,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cors@npm:^2.8.5":
version: 2.8.5
resolution: "cors@npm:2.8.5"
dependencies:
object-assign: "npm:^4"
vary: "npm:^1"
checksum: 373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761
languageName: node
linkType: hard
"cosmiconfig@npm:^7.0.0": "cosmiconfig@npm:^7.0.0":
version: 7.1.0 version: 7.1.0
resolution: "cosmiconfig@npm:7.1.0" resolution: "cosmiconfig@npm:7.1.0"
@ -6147,7 +6168,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"css-declaration-sorter@npm:^7.0.0": "css-declaration-sorter@npm:^7.1.1":
version: 7.1.1 version: 7.1.1
resolution: "css-declaration-sorter@npm:7.1.1" resolution: "css-declaration-sorter@npm:7.1.1"
peerDependencies: peerDependencies:
@ -6235,7 +6256,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"css-tree@npm:^2.2.1, css-tree@npm:^2.3.1": "css-tree@npm:^2.3.1":
version: 2.3.1 version: 2.3.1
resolution: "css-tree@npm:2.3.1" resolution: "css-tree@npm:2.3.1"
dependencies: dependencies:
@ -6285,42 +6306,42 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cssnano-preset-default@npm:^6.0.2": "cssnano-preset-default@npm:^6.0.3":
version: 6.0.2 version: 6.0.3
resolution: "cssnano-preset-default@npm:6.0.2" resolution: "cssnano-preset-default@npm:6.0.3"
dependencies: dependencies:
css-declaration-sorter: "npm:^7.0.0" css-declaration-sorter: "npm:^7.1.1"
cssnano-utils: "npm:^4.0.1" cssnano-utils: "npm:^4.0.1"
postcss-calc: "npm:^9.0.1" postcss-calc: "npm:^9.0.1"
postcss-colormin: "npm:^6.0.1" postcss-colormin: "npm:^6.0.2"
postcss-convert-values: "npm:^6.0.1" postcss-convert-values: "npm:^6.0.2"
postcss-discard-comments: "npm:^6.0.1" postcss-discard-comments: "npm:^6.0.1"
postcss-discard-duplicates: "npm:^6.0.1" postcss-discard-duplicates: "npm:^6.0.1"
postcss-discard-empty: "npm:^6.0.1" postcss-discard-empty: "npm:^6.0.1"
postcss-discard-overridden: "npm:^6.0.1" postcss-discard-overridden: "npm:^6.0.1"
postcss-merge-longhand: "npm:^6.0.1" postcss-merge-longhand: "npm:^6.0.2"
postcss-merge-rules: "npm:^6.0.2" postcss-merge-rules: "npm:^6.0.3"
postcss-minify-font-values: "npm:^6.0.1" postcss-minify-font-values: "npm:^6.0.1"
postcss-minify-gradients: "npm:^6.0.1" postcss-minify-gradients: "npm:^6.0.1"
postcss-minify-params: "npm:^6.0.1" postcss-minify-params: "npm:^6.0.2"
postcss-minify-selectors: "npm:^6.0.1" postcss-minify-selectors: "npm:^6.0.2"
postcss-normalize-charset: "npm:^6.0.1" postcss-normalize-charset: "npm:^6.0.1"
postcss-normalize-display-values: "npm:^6.0.1" postcss-normalize-display-values: "npm:^6.0.1"
postcss-normalize-positions: "npm:^6.0.1" postcss-normalize-positions: "npm:^6.0.1"
postcss-normalize-repeat-style: "npm:^6.0.1" postcss-normalize-repeat-style: "npm:^6.0.1"
postcss-normalize-string: "npm:^6.0.1" postcss-normalize-string: "npm:^6.0.1"
postcss-normalize-timing-functions: "npm:^6.0.1" postcss-normalize-timing-functions: "npm:^6.0.1"
postcss-normalize-unicode: "npm:^6.0.1" postcss-normalize-unicode: "npm:^6.0.2"
postcss-normalize-url: "npm:^6.0.1" postcss-normalize-url: "npm:^6.0.1"
postcss-normalize-whitespace: "npm:^6.0.1" postcss-normalize-whitespace: "npm:^6.0.1"
postcss-ordered-values: "npm:^6.0.1" postcss-ordered-values: "npm:^6.0.1"
postcss-reduce-initial: "npm:^6.0.1" postcss-reduce-initial: "npm:^6.0.2"
postcss-reduce-transforms: "npm:^6.0.1" postcss-reduce-transforms: "npm:^6.0.1"
postcss-svgo: "npm:^6.0.1" postcss-svgo: "npm:^6.0.2"
postcss-unique-selectors: "npm:^6.0.1" postcss-unique-selectors: "npm:^6.0.2"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: c6f97674704c3a2a2473440549eac38ac722feebabbd39f2d4d1b8fae7f137f8fd0dfb88929e1ff737d54008de583c39e96f9dc450f2d71f8be6fc3bac2840a3 checksum: d100a1f8ab71adbb6df85e00f4a9e5d04ac06fc50343157eef853aded3f75dd0489dd845a5b2fb43ca701bd88c39c5aa88673f842bc1f94f4318c7b38ced1963
languageName: node languageName: node
linkType: hard linkType: hard
@ -6334,23 +6355,14 @@ __metadata:
linkType: hard linkType: hard
"cssnano@npm:^6.0.1": "cssnano@npm:^6.0.1":
version: 6.0.2 version: 6.0.3
resolution: "cssnano@npm:6.0.2" resolution: "cssnano@npm:6.0.3"
dependencies: dependencies:
cssnano-preset-default: "npm:^6.0.2" cssnano-preset-default: "npm:^6.0.3"
lilconfig: "npm:^3.0.0" lilconfig: "npm:^3.0.0"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 5f4146a6c8937d24b0d1d33e3acd85db7913c7558cc80b23169f86c9a552d091a26e0af6adcc535f8355561872f797a917b9353e38fe935bbaf08ec2b66f5ff8 checksum: d1669eb987fd96159bae262ef2f76c1a64fffefe8fa593918a6bda377977798b60fb4a6a871a9b9a9deb11258130ee254fdb8c3144769b3060ad9f2a95a4ed0a
languageName: node
linkType: hard
"csso@npm:5.0.5":
version: 5.0.5
resolution: "csso@npm:5.0.5"
dependencies:
css-tree: "npm:~2.2.0"
checksum: ab4beb1e97dd7e207c10e9925405b45f15a6cd1b4880a8686ad573aa6d476aed28b4121a666cffd26c37a26179f7b54741f7c257543003bfb244d06a62ad569b
languageName: node languageName: node
linkType: hard linkType: hard
@ -6363,6 +6375,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"csso@npm:^5.0.5":
version: 5.0.5
resolution: "csso@npm:5.0.5"
dependencies:
css-tree: "npm:~2.2.0"
checksum: ab4beb1e97dd7e207c10e9925405b45f15a6cd1b4880a8686ad573aa6d476aed28b4121a666cffd26c37a26179f7b54741f7c257543003bfb244d06a62ad569b
languageName: node
linkType: hard
"cssom@npm:^0.5.0": "cssom@npm:^0.5.0":
version: 0.5.0 version: 0.5.0
resolution: "cssom@npm:0.5.0" resolution: "cssom@npm:0.5.0"
@ -8191,13 +8212,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.0": "follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.4":
version: 1.15.3 version: 1.15.4
resolution: "follow-redirects@npm:1.15.3" resolution: "follow-redirects@npm:1.15.4"
peerDependenciesMeta: peerDependenciesMeta:
debug: debug:
optional: true optional: true
checksum: 915a2cf22e667bdf47b1a43cc6b7dce14d95039e9bbf9a24d0e739abfbdfa00077dd43c86d4a7a19efefcc7a99af144920a175eedc3888d268af5df67c272ee5 checksum: 5f37ed9170c9eb19448c5418fdb0f2b73f644b5364834e70791a76ecc7db215246f9773bbef4852cfae4067764ffc852e047f744b661b0211532155b73556a6a
languageName: node languageName: node
linkType: hard linkType: hard
@ -11981,7 +12002,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1":
version: 4.1.1 version: 4.1.1
resolution: "object-assign@npm:4.1.1" resolution: "object-assign@npm:4.1.1"
checksum: 1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 checksum: 1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414
@ -12750,29 +12771,29 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-colormin@npm:^6.0.1": "postcss-colormin@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-colormin@npm:6.0.1" resolution: "postcss-colormin@npm:6.0.2"
dependencies: dependencies:
browserslist: "npm:^4.21.4" browserslist: "npm:^4.22.2"
caniuse-api: "npm:^3.0.0" caniuse-api: "npm:^3.0.0"
colord: "npm:^2.9.1" colord: "npm:^2.9.1"
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: b0056812b3436b05b6b84284a1ebe68a72299f23e7eeb0b7b40a775978d06a1cbe235f3665e3f694f5de76fe7d9b93db607536d07697b31a59fd4e8705e5b64d checksum: 229681f9b89ba0909b4c69563837b0c32cc3d1c17ed1b00c33d4abfb0a0ef455124968e4885b5f92c64482e92074cd1958018ec111ed5d118f1e24baeda19c14
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-convert-values@npm:^6.0.1": "postcss-convert-values@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-convert-values@npm:6.0.1" resolution: "postcss-convert-values@npm:6.0.2"
dependencies: dependencies:
browserslist: "npm:^4.21.4" browserslist: "npm:^4.22.2"
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 53b951d7475206969c63b8427a2dea0ccba0a7cb08122e5f05aee8d12b09c870c070b101c9f8eceda76ff4d0fd9e5fa9385e83f143d658bb729dbb6a3583b872 checksum: 882d0b7839ef07ac8ffbf9cb48db0f610939a3496bd0321c7f23096ead676f13e09ab3d9c20ff3dbe2c887e855826051ca7dffeaffce5068cfdc9aaa573a3842
languageName: node languageName: node
linkType: hard linkType: hard
@ -12835,29 +12856,29 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-merge-longhand@npm:^6.0.1": "postcss-merge-longhand@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-merge-longhand@npm:6.0.1" resolution: "postcss-merge-longhand@npm:6.0.2"
dependencies: dependencies:
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
stylehacks: "npm:^6.0.1" stylehacks: "npm:^6.0.2"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 2c0eb81b6c6d3d2af3b129c46d10317b7923f218db1cadcb4723091fb951fe4624638002b65f235151129d4ce9b4775a6ed0d5fa13419c0df580f72e15fa4ad3 checksum: 2b3fae51bffc5962258d638bc7f415237593b515f369233e023f0eae5b13116297463c04b8c47a7b7af51cba5faaa7f517b653f6123e51935d670d4d4de5a26d
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-merge-rules@npm:^6.0.2": "postcss-merge-rules@npm:^6.0.3":
version: 6.0.2 version: 6.0.3
resolution: "postcss-merge-rules@npm:6.0.2" resolution: "postcss-merge-rules@npm:6.0.3"
dependencies: dependencies:
browserslist: "npm:^4.21.4" browserslist: "npm:^4.22.2"
caniuse-api: "npm:^3.0.0" caniuse-api: "npm:^3.0.0"
cssnano-utils: "npm:^4.0.1" cssnano-utils: "npm:^4.0.1"
postcss-selector-parser: "npm:^6.0.5" postcss-selector-parser: "npm:^6.0.15"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 138a9921423420116b20e5761a1139392f0bcfcf34264fe11e254917d9c3170e3c0478a1b409e227d22bb0d9820b0168a871a240215d114e9c1e218ee6c132e6 checksum: c8355db11aa60bedcb1e6535fcd70f6ecec2dadd5c2975d3accf0eedbc92af782ac1f5e91a53866816ce332e4cbf1b94749a9425067935be066bc0c974e30fee
languageName: node languageName: node
linkType: hard linkType: hard
@ -12885,27 +12906,27 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-minify-params@npm:^6.0.1": "postcss-minify-params@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-minify-params@npm:6.0.1" resolution: "postcss-minify-params@npm:6.0.2"
dependencies: dependencies:
browserslist: "npm:^4.21.4" browserslist: "npm:^4.22.2"
cssnano-utils: "npm:^4.0.1" cssnano-utils: "npm:^4.0.1"
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 0b34817f032ec9793fad4d33f3ba5551531073a36c9120d77194a3edeee860132951ed6954913494e5a6752ae8da1bc5cdb2a44fa5f428621afae8edddb0ca80 checksum: 6638460d2be4a2eca8adee8409b70d6c6a19aff8cf93fda1b45c9da627b258b6baaa6acb48f51d26cd287704a235f9c9ae2e4744335b1fd47e163177c33896df
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-minify-selectors@npm:^6.0.1": "postcss-minify-selectors@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-minify-selectors@npm:6.0.1" resolution: "postcss-minify-selectors@npm:6.0.2"
dependencies: dependencies:
postcss-selector-parser: "npm:^6.0.5" postcss-selector-parser: "npm:^6.0.15"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: ffc7ebb286beda2b2aa0ed13abafc89b5ffe232a48d57d3f2b9f69e167e354482a6f5279e9118bed753bf6e82d3cfb21228a6b07acd93d0dc9e01bbf0e7ebc75 checksum: 5437b586c1237fc442e7e6078d4f23c987efc456366368b07a0da67332b04bd55821cedf0441e73e1209689f63139e272d930508e2963ba6e27c46561a661128
languageName: node languageName: node
linkType: hard linkType: hard
@ -13017,15 +13038,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-normalize-unicode@npm:^6.0.1": "postcss-normalize-unicode@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-normalize-unicode@npm:6.0.1" resolution: "postcss-normalize-unicode@npm:6.0.2"
dependencies: dependencies:
browserslist: "npm:^4.21.4" browserslist: "npm:^4.22.2"
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 8057748dade94dc2dd63a3b75a85e394c2e9a7076053886ff08aa9b7729d383f204eda52d882e5361ae1ec493036e90b2e18dcc5f8c9b3a8f1cbfada12bcc05b checksum: ea696194f65ad31de2a9c022f1946a07c298f04070706d88a20061845e1e052e645c74b5bc785595814db87d14e435f85e968a44855dedc207d8c0b5d43b1aee
languageName: node languageName: node
linkType: hard linkType: hard
@ -13063,15 +13084,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-reduce-initial@npm:^6.0.1": "postcss-reduce-initial@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-reduce-initial@npm:6.0.1" resolution: "postcss-reduce-initial@npm:6.0.2"
dependencies: dependencies:
browserslist: "npm:^4.21.4" browserslist: "npm:^4.22.2"
caniuse-api: "npm:^3.0.0" caniuse-api: "npm:^3.0.0"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 3f8f6c26ceeb79ddc285b0e01183fe30e911dd26b3abcdca56568e2bef3747f2b7f22ee3f9117e9752e1e93c10bcd88bd6a2842ca525b54336726292ebd3c3ad checksum: d35ad6f9725cdceb390a97a461e8594df7fbed4c55497c90d07c42f8343bf80139e720eaebc580bf480bf10e92959490aa308af66d8802ba71c327bdf08c93a1
languageName: node languageName: node
linkType: hard linkType: hard
@ -13111,36 +13132,36 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.13, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5": "postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.13, postcss-selector-parser@npm:^6.0.15, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4":
version: 6.0.13 version: 6.0.15
resolution: "postcss-selector-parser@npm:6.0.13" resolution: "postcss-selector-parser@npm:6.0.15"
dependencies: dependencies:
cssesc: "npm:^3.0.0" cssesc: "npm:^3.0.0"
util-deprecate: "npm:^1.0.2" util-deprecate: "npm:^1.0.2"
checksum: 51f099b27f7c7198ea1826470ef0adfa58b3bd3f59b390fda123baa0134880a5fa9720137b6009c4c1373357b144f700b0edac73335d0067422063129371444e checksum: 48b425d6cef497bcf6b7d136f6fd95cfca43026955e07ec9290d3c15457de3a862dbf251dd36f42c07a0d5b5ab6f31e41acefeff02528995a989b955505e440b
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-svgo@npm:^6.0.1": "postcss-svgo@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-svgo@npm:6.0.1" resolution: "postcss-svgo@npm:6.0.2"
dependencies: dependencies:
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
svgo: "npm:^3.0.5" svgo: "npm:^3.2.0"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 021da9b0d0696fce970f407891a0d6c05e51d1908af435026e0cd5936a75cd8502a7d504cd0e6a33b6f3369fee41f01b848e5bd919aecc3e804ce6308e91a6cc checksum: db607404d09af256c7957a0ace822d651a00a52a1796da603f93ba3f0a095ac7595e1f624b9dc53f362ab10e382845d7873f485980f9c92fcb86256833f5e835
languageName: node languageName: node
linkType: hard linkType: hard
"postcss-unique-selectors@npm:^6.0.1": "postcss-unique-selectors@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "postcss-unique-selectors@npm:6.0.1" resolution: "postcss-unique-selectors@npm:6.0.2"
dependencies: dependencies:
postcss-selector-parser: "npm:^6.0.5" postcss-selector-parser: "npm:^6.0.15"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 637e35775d0ee8fbcf4a81b28d3832c5076de7c0232eb7769d4fbbf783f26793e2ec95e18461ae3b9f5f5cd63c3de9db102464487ba2488d4947aad24dc8841f checksum: a0fe112d1094f90e1bfcfd2174a74b2fd0630a24449e9942923d02956c7d64ea4add5adede53d9efb3f6d40cd388ac150d032a115f6a46b73d5f3d3d26fa1bb7
languageName: node languageName: node
linkType: hard linkType: hard
@ -13708,8 +13729,8 @@ __metadata:
linkType: hard linkType: hard
"react-redux-loading-bar@npm:^5.0.4": "react-redux-loading-bar@npm:^5.0.4":
version: 5.0.7 version: 5.0.8
resolution: "react-redux-loading-bar@npm:5.0.7" resolution: "react-redux-loading-bar@npm:5.0.8"
dependencies: dependencies:
prop-types: "npm:^15.7.2" prop-types: "npm:^15.7.2"
react-lifecycles-compat: "npm:^3.0.4" react-lifecycles-compat: "npm:^3.0.4"
@ -13718,7 +13739,7 @@ __metadata:
react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-redux: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 react-redux: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
redux: ^3.0.0 || ^4.0.0 || ^5.0.0 redux: ^3.0.0 || ^4.0.0 || ^5.0.0
checksum: 45333093e7d28df923a657ad89ffe4673d7bd135ef57c0143fb4d868f21b57aeb9044691f553f7d2afbcc9080a1f8cd3cec5b274c80cb57faf0e87a70f7a2cce checksum: 797c1abf8bcc947feb127380e6d363db264c12bc94e578d635f86f1d806b0ec714dc3723e54c884937448b17f9042cfc995fe7a1deaf558efc01681e43e4669c
languageName: node languageName: node
linkType: hard linkType: hard
@ -15620,15 +15641,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"stylehacks@npm:^6.0.1": "stylehacks@npm:^6.0.2":
version: 6.0.1 version: 6.0.2
resolution: "stylehacks@npm:6.0.1" resolution: "stylehacks@npm:6.0.2"
dependencies: dependencies:
browserslist: "npm:^4.21.4" browserslist: "npm:^4.22.2"
postcss-selector-parser: "npm:^6.0.4" postcss-selector-parser: "npm:^6.0.15"
peerDependencies: peerDependencies:
postcss: ^8.4.31 postcss: ^8.4.31
checksum: 0877016f5b2a06b8ceaf39382b0c33da11ea93268209444f67f29b1ce465994058f305fc3bc90dda21e8664c959561fbb06ba12b82289c3b26ba832c6979d513 checksum: 658cac8b28edcb94d1db67808ab3aaa511cb1b9293594fc95607ee42ac4f57e742d9a1fa3ff5d5849db692971dc2a310e9ac1ed0bd4ea4bc48c80f5a6ef823fc
languageName: node languageName: node
linkType: hard linkType: hard
@ -15658,30 +15679,30 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-standard-scss@npm:^12.0.0": "stylelint-config-standard-scss@npm:^13.0.0":
version: 12.0.0 version: 13.0.0
resolution: "stylelint-config-standard-scss@npm:12.0.0" resolution: "stylelint-config-standard-scss@npm:13.0.0"
dependencies: dependencies:
stylelint-config-recommended-scss: "npm:^14.0.0" stylelint-config-recommended-scss: "npm:^14.0.0"
stylelint-config-standard: "npm:^35.0.0" stylelint-config-standard: "npm:^36.0.0"
peerDependencies: peerDependencies:
postcss: ^8.3.3 postcss: ^8.3.3
stylelint: ^16.0.2 stylelint: ^16.1.0
peerDependenciesMeta: peerDependenciesMeta:
postcss: postcss:
optional: true optional: true
checksum: 7f3ccfb4175f9c50b69d30ca35a97887008c5ba493dbe7d5bce0b57b1eafd21b268177b82404368e7780600077cba784f98e1046671724be3b29a00c6a7913a4 checksum: 4abf317676184f4aaace6ce72b9fc9e2dffe051d43dd5637afc5803b062ea381e2807ae983c045dff22e96af58388a8b1fe9a8bdda9f97bc3660280cf24fb4d3
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-standard@npm:^35.0.0": "stylelint-config-standard@npm:^36.0.0":
version: 35.0.0 version: 36.0.0
resolution: "stylelint-config-standard@npm:35.0.0" resolution: "stylelint-config-standard@npm:36.0.0"
dependencies: dependencies:
stylelint-config-recommended: "npm:^14.0.0" stylelint-config-recommended: "npm:^14.0.0"
peerDependencies: peerDependencies:
stylelint: ^16.0.0 stylelint: ^16.1.0
checksum: 791fbc26cc3029ce3c2423a643e903545b5e4cd605251b18f0ce790bac6fbaaf380469845c1ff45f4e320126af9f8a9dc1ca85d0df9274277ae60da91e81895b checksum: 1fc9adddfc5cf0a1d7a443182a0731712a3950ace72a24081b4ede2b0bb6fc1eebd003c009f1d8d06c3a64ba9b31b0ed12512db2f91c8fa549238d8341580e4b
languageName: node languageName: node
linkType: hard linkType: hard
@ -15852,20 +15873,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"svgo@npm:^3.0.5": "svgo@npm:^3.2.0":
version: 3.1.0 version: 3.2.0
resolution: "svgo@npm:3.1.0" resolution: "svgo@npm:3.2.0"
dependencies: dependencies:
"@trysound/sax": "npm:0.2.0" "@trysound/sax": "npm:0.2.0"
commander: "npm:^7.2.0" commander: "npm:^7.2.0"
css-select: "npm:^5.1.0" css-select: "npm:^5.1.0"
css-tree: "npm:^2.2.1" css-tree: "npm:^2.3.1"
css-what: "npm:^6.1.0" css-what: "npm:^6.1.0"
csso: "npm:5.0.5" csso: "npm:^5.0.5"
picocolors: "npm:^1.0.0" picocolors: "npm:^1.0.0"
bin: bin:
svgo: ./bin/svgo svgo: ./bin/svgo
checksum: b3f00b3319dee6ddc53f8b8ac5acef581860e1708c98b492169e096621edc1bdf46e3778099e3dffb5116bf0d4c074a686099843dbc020c73b3ccfae7b6a88f0 checksum: 28fa9061ccbcf2e3616d48d1feb613aaa05f8f290a329beb0e585914f1864385152934a7d4d683a4609fafbae3d51666633437c359c5c5ef74fb58ad09092a7c
languageName: node languageName: node
linkType: hard linkType: hard
@ -16772,7 +16793,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vary@npm:~1.1.2": "vary@npm:^1, vary@npm:~1.1.2":
version: 1.1.2 version: 1.1.2
resolution: "vary@npm:1.1.2" resolution: "vary@npm:1.1.2"
checksum: f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f checksum: f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f