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

View file

@ -167,7 +167,6 @@ Rails/WhereExists:
- 'app/validators/reaction_validator.rb'
- 'app/validators/vote_validator.rb'
- 'app/workers/move_worker.rb'
- 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb'
- 'lib/tasks/tests.rake'
- 'spec/models/account_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb'
@ -255,8 +254,6 @@ Style/GuardClause:
- 'app/workers/redownload_media_worker.rb'
- 'app/workers/remote_account_refresh_worker.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_pam_authenticatable.rb'
- 'lib/mastodon/cli/accounts.rb'
@ -277,7 +274,6 @@ Style/HashAsLastArrayItem:
- 'app/models/status.rb'
- 'app/services/batched_remove_status_service.rb'
- 'app/services/notify_service.rb'
- 'db/migrate/20181024224956_migrate_account_conversations.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/HashTransformValues:

View file

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

View file

@ -40,7 +40,7 @@ module Admin
(@email_domain_block.other_domains || []).uniq.each do |domain|
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
end
end
@ -65,7 +65,7 @@ module Admin
end
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
def form_email_domain_block_batch_params

View file

@ -68,7 +68,7 @@ module Admin
def export_data
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]
end
end

View file

@ -55,7 +55,7 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
end
def resource_params
params.permit(:domain)
params.permit(:domain, :allow_with_approval)
end
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?
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?
# 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) }
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?
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
end
def build_signed_string
def build_signed_string(include_query_string: true)
signed_headers.map do |signed_header|
case signed_header
when Request::REQUEST_TARGET
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
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}"
end
when '(created)'
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?

View file

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

View file

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

View file

@ -38,12 +38,14 @@
"confirmation_modal.cancel": "Cancellar",
"confirmations.delete.confirm": "Deler",
"confirmations.delete_list.confirm": "Deler",
"confirmations.edit.confirm": "Modificar",
"confirmations.logout.confirm": "Clauder le session",
"copy_icon_button.copied": "Copiate al area de transferentia",
"copypaste.copy_to_clipboard": "Copiar al area de transferentia",
"disabled_account_banner.account_settings": "Parametros de conto",
"dismissable_banner.dismiss": "Dimitter",
"emoji_button.activity": "Activitate",
"emoji_button.clear": "Rader",
"emoji_button.custom": "Personalisate",
"emoji_button.search_results": "Resultatos de recerca",
"empty_column.account_unavailable": "Profilo non disponibile",
@ -69,16 +71,37 @@
"navigation_bar.about": "A proposito de",
"navigation_bar.advanced_interface": "Aperir in un interfacie web avantiate",
"navigation_bar.blocks": "Usatores blocate",
"navigation_bar.discover": "Discoperir",
"navigation_bar.edit_profile": "Modificar profilo",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session",
"navigation_bar.preferences": "Preferentias",
"navigation_bar.search": "Cercar",
"navigation_bar.security": "Securitate",
"notifications.column_settings.alert": "Notificationes de scriptorio",
"notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias",
"notifications.column_settings.sound": "Reproducer sono",
"notifications.filter.all": "Toto",
"notifications.grant_permission": "Conceder permission.",
"notifications.group": "{count} notificationes",
"onboarding.compose.template": "Salute #Mastodon!",
"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.share_profile.body": "Informe a tus amigos komo toparte en Mastodon",
"onboarding.steps.share_profile.title": "Partaja tu profil de Mastodon",
"password_confirmation.mismatching": "Los dos kodes son desferentes",
"picture_in_picture.restore": "Restora",
"poll.closed": "Serrado",
"poll.refresh": "Arefreska",

View file

@ -606,7 +606,7 @@
"search.quick_action.status_search": "Innlegg som samsvarer med {x}",
"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_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.options": "Søkjealternativ",
"search_popout.quick_actions": "Hurtighandlinger",

View file

@ -606,7 +606,7 @@
"search.quick_action.status_search": "Innlegg som samsvarer med {x}",
"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_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.options": "Alternativer for søk",
"search_popout.quick_actions": "Hurtighandlinger",

View file

@ -77,6 +77,7 @@ class Request
@url = Addressable::URI.parse(url).normalize
@http_client = options.delete(:http_client)
@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(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT)
@options = @options.merge(proxy_url) if use_proxy?
@ -146,7 +147,7 @@ class Request
private
def set_common_headers!
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
@headers[REQUEST_TARGET] = request_target
@headers['User-Agent'] = Mastodon::Version.user_agent
@headers['Host'] = @url.host
@headers['Date'] = Time.now.utc.httpdate
@ -157,6 +158,14 @@ class Request
@headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
end
def request_target
if @url.query.nil? || !@full_path
"#{@verb} #{@url.path}"
else
"#{@verb} #{@url.path}?#{@url.query}"
end
end
def signature
algorithm = 'rsa-sha256'
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
# type variants, and browsers often send the wrong one,
# 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
# `file` utility instead
INCORRECT_CONTENT_TYPES = %w(
audio/vorbis
audio/opus
video/ogg
video/webm
).freeze

View file

@ -4,11 +4,12 @@
#
# Table name: email_domain_blocks
#
# id :bigint(8) not null, primary key
# domain :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# parent_id :bigint(8)
# id :bigint(8) not null, primary key
# domain :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# parent_id :bigint(8)
# allow_with_approval :boolean default(FALSE), not null
#
class EmailDomainBlock < ApplicationRecord
@ -42,8 +43,8 @@ class EmailDomainBlock < ApplicationRecord
@attempt_ip = attempt_ip
end
def match?
blocking? || invalid_uri?
def match?(...)
blocking?(...) || invalid_uri?
end
private
@ -52,8 +53,8 @@ class EmailDomainBlock < ApplicationRecord
@uris.any?(&:nil?)
end
def blocking?
blocks = EmailDomainBlock.where(domain: domains_with_variants).order(Arel.sql('char_length(domain) desc'))
def blocking?(allow_with_approval: false)
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.any?
end
@ -86,4 +87,8 @@ class EmailDomainBlock < ApplicationRecord
def self.block?(domain_or_domains, attempt_ip: nil)
Matcher.new(domain_or_domains, attempt_ip: attempt_ip).match?
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

View file

@ -420,7 +420,7 @@ class User < ApplicationRecord
def set_approved
self.approved = begin
if sign_up_from_ip_requires_approval?
if sign_up_from_ip_requires_approval? || sign_up_email_requires_approval?
false
else
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?
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?
Setting.registrations_mode == 'open'
end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class REST::Admin::EmailDomainBlockSerializer < ActiveModel::Serializer
attributes :id, :domain, :created_at, :history
attributes :id, :domain, :created_at, :history, :allow_with_approval
def id
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 })
- if email_domain_block.allow_with_approval?
·
= t('admin.email_domain_blocks.allow_registrations_with_approval')

View file

@ -7,6 +7,9 @@
.fields-group
= 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)
%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?
"ws#{https ? 's' : ''}://#{web_host}"
else
"ws://#{ENV['REMOTE_DEV'] == 'true' ? host.split(':').first : 'localhost'}:4000"
"ws://#{host.split(':').first}:4000"
end
end

View file

@ -46,6 +46,10 @@ ie:
title: 2FA desvalidat
two_factor_enabled:
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:
subject: 'Mastodon: Desserral instructiones'
webauthn_credential:
@ -55,8 +59,11 @@ ie:
webauthn_disabled:
subject: 'Mastodon: Autentication con claves de securitá desactivisat'
title: Claves de securitá desactivisat
webauthn_enabled:
title: Claves de securitá activisat
omniauth_callbacks:
failure: Ne posset autenticar te de %{kind} pro "%{reason}".
success: Successosimen autenticat de conto %{kind}.
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.
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
index:
application: Aplication
callback_url: URL de retrovocada
delete: Deleter
empty: Tu have null aplicationes.
name: Nómine
@ -42,6 +43,7 @@ ie:
show:
actions: Actiones
application_id: Clave de client
callback_urls: URLs de retrovocada
secret: Secrete de client
title: 'Aplication: %{name}'
authorizations:
@ -51,6 +53,7 @@ ie:
error:
title: Alquo ha errat
new:
review_permissions: Inspecter permissiones
title: Autorisation besonat
authorized_applications:
buttons:

View file

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

View file

@ -383,7 +383,11 @@ lad:
confirm_suspension:
cancel: Anula
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}
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
destroyed_msg: El bloko de domeno se dezizo
domain: Domeno
@ -772,6 +776,8 @@ lad:
type: Tipo
types:
major: Versyon prinsipala
minor: Versyon minora
patch: Versyon de remendo koreksyones de yerros i trokamientos simples
version: Versyon
statuses:
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
software_version_critical_check:
action: Amostra aktualizasyones desponivles
message_html: Una aktualizasyon kritika de Mastodon esta desponivle. Por favor aktualiza pishin.
software_version_patch_check:
action: Amostra aktualizasyones desponivles
message_html: Una aktualizasyon de Mastodon kon koreksyon de yerros esta desponivle.
upload_check_privacy_error:
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>"
@ -945,6 +953,7 @@ lad:
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}"
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}!
new_pending_account:
body: Los peratim del muevo kuento estan abashos. Puedes achetar o refuzar esta aplikasyon.
@ -1045,13 +1054,17 @@ lad:
accept: Acheta
back: Atras
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_invited: Fuites envitado.
security: Sigurita
set_new_password: Establese muevo kod
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_settings_hint_html: Klika el atadjiko ke te embimos para verifikar %{email}. Asperaremos aki.
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:
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}
@ -1246,9 +1259,11 @@ lad:
imports:
errors:
empty: Dosya CSV vaziya
incompatible_type: Inkompativle kon el tipo de importo eskojido
invalid_csv_file: 'Dosya CSV no valida. Yerro: %{error}'
over_rows_processing_limit: kontiene mas de %{count} filas
too_large: Dosya es mas grande
failures: Yerros
imported: Importado
modes:
merge: Une
@ -1671,6 +1686,9 @@ lad:
month: "%b %Y"
time: "%H:%M"
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:
add: Adjusta
disable: Inkapasita autentifikasyon en dos pasos
@ -1750,9 +1768,12 @@ lad:
title: Bienvenido, %{name}!
users:
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
otp_lost_help_html: Si pedriste akseso a los dos, puedes kontaktarte kon %{email}
signed_in_as: 'Konektado komo:'
verification:
here_is_how: Ansina es komo
verification: Verifikasyon
verified_links: Tus atadijos verifikados
webauthn_credentials:

View file

@ -86,6 +86,7 @@ ie:
ip_block:
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.
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:
no_access: Bloccar accesse a omni ressurses
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
rule:
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:
role: Permissiones de usator decidet per su rol
user_role:
@ -111,6 +116,7 @@ ie:
name: Etiquette
value: Contenete
indexable: Includer public postas in resultates de sercha
unlocked: Automaticmen acceptar nov sequitores
account_alias:
acct: Usator-nómine del anteyan conto
account_migration:
@ -158,6 +164,7 @@ ie:
max_uses: Max grand númere de usas
new_password: Nov passa-parol
note: Biografie
otp_attempt: 2-factor code
password: Passa-parol
phrase: Clave-parol o frase
setting_advanced_layout: Possibilisar web-interfacie avansat
@ -165,10 +172,12 @@ ie:
setting_default_language: Lingue in quel postar
setting_default_privacy: Privatie de postada
setting_default_sensitive: Sempre marcar medie quam sensitiv
setting_display_media: Exposition de medie
setting_display_media_default: Predefinitiones
setting_display_media_hide_all: Celar omno
setting_display_media_show_all: Monstrar omno
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_theme: Tema de situ
setting_trends: Monstrar li hodial tendenties
@ -179,6 +188,7 @@ ie:
title: Titul
type: Specie de importation
username: Nómine de usator
username_or_email: Usator-nómine o E-posta
whole_word: Plen parol
featured_tag:
name: Hashtag
@ -193,11 +203,13 @@ ie:
custom_css: Custom CSS
profile_directory: Possibilisar profilarium
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_rationale: Monstrar pro quo cert dominias esset bloccat
site_contact_email: Contact e-mail adresse
site_contact_username: Usator-nómine de contact
site_extended_description: Extendet descrition
site_short_description: Descrition del servitor
site_title: Nómine de servitor
theme: Predefenit tema
trendable_by_default: Possibilisar tendenties sin priori inspection

View file

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

View file

@ -105,7 +105,7 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
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)
migrated += 1

View file

@ -9,7 +9,7 @@ class PreserveOldLayoutForExistingUsers < ActiveRecord::Migration[5.2]
# on the to-be-changed default
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
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
def up
owner_role = UserRole.find_by(name: 'Owner')
admin_role = UserRole.find_by(name: 'Admin')
moderator_role = UserRole.find_by(name: 'Moderator')
everyone_role = UserRole.find_by(id: -99)
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.save
end
if owner_role
owner_role.highlighted = show_staff_badge
owner_role.save
end
if admin_role
admin_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(admin moderator).include?(min_invite_role)
admin_role.highlighted = show_staff_badge
admin_role.save
end
if moderator_role
moderator_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(moderator).include?(min_invite_role)
moderator_role.highlighted = show_staff_badge
moderator_role.save
end
process_role_everyone
process_role_owner
process_role_admin
process_role_moderator
end
def down; end
private
def process_role_everyone
everyone_role = UserRole.find_by(id: -99)
return unless everyone_role
everyone_role.permissions &= ~::UserRole::FLAGS[:invite_users] unless min_invite_role == 'user'
everyone_role.save
end
def process_role_owner
owner_role = UserRole.find_by(name: 'Owner')
return unless owner_role
owner_role.highlighted = show_staff_badge
owner_role.save
end
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.highlighted = show_staff_badge
admin_role.save
end
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.highlighted = show_staff_badge
moderator_role.save
end
def min_invite_role
Setting.min_invite_role
end
def show_staff_badge
Setting.show_staff_badge
end
end

View file

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

View file

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

View file

@ -10,7 +10,7 @@
#
# 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
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 "updated_at", precision: nil, null: false
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
end

View file

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

View file

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'tty-prompt'
module Mastodon::CLI
module Federation
extend ActiveSupport::Concern
@ -30,44 +28,50 @@ module Mastodon::CLI
LONG_DESC
def 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
sidekiq_stats = Sidekiq::Stats.new
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?
prompt.warn('Deletion notices are still being processed')
say('Deletion notices are still being processed', :yellow)
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
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
exit(0)
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.')
prompt.warn('While the data won\'t be erased locally, the server will be in a BROKEN STATE afterwards.')
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).')
say(<<~WARNING, :yellow)
This operation WILL NOT be reversible.
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)
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:')
prompt.ok(" SELF_DESTRUCT=#{self_destruct_value}")
prompt.ok("\nYou can re-run this command to see the state of the self-destruct process.")
rescue TTY::Reader::InputInterrupt
say(<<~INSTRUCTIONS, :green)
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:
SELF_DESTRUCT=#{self_destruct_value}
You can re-run this command to see the state of the self-destruct process.
INSTRUCTIONS
rescue Interrupt
exit(1)
end
private
def prompt
@prompt ||= TTY::Prompt.new
def self_destruct_value
Rails
.application
.message_verifier('self-destruct')
.generate(Rails.configuration.x.local_domain)
end
end
end

View file

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

View file

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

View file

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

View file

@ -135,6 +135,25 @@ RSpec.describe Auth::RegistrationsController do
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
subject do
Setting.registrations_mode = 'approved'

View file

@ -22,7 +22,7 @@ describe 'Admin::Accounts' do
context 'without selecting any accounts' 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)
end
@ -32,7 +32,7 @@ describe 'Admin::Accounts' do
it 'suspends the account' do
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
end
@ -42,7 +42,7 @@ describe 'Admin::Accounts' do
it 'approves the account user' do
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
end
@ -52,7 +52,7 @@ describe 'Admin::Accounts' do
it 'rejects and removes the account' do
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)
end

View file

@ -16,7 +16,7 @@ describe 'Admin::CustomEmojis' do
context 'without selecting any records' 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)
end

View file

@ -14,7 +14,7 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'example.com'
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(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'
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
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
expect(DomainBlockWorker).to_not have_received(:perform_async)
# 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(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'
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
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
expect(DomainBlockWorker).to_not have_received(:perform_async)
# 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(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'
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
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)
# 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(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)
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
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
expect(DomainBlockWorker).to_not have_received(:perform_async)
# 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(domain_block.reload.severity).to eq 'suspend'

View file

@ -16,7 +16,7 @@ describe 'Admin::EmailDomainBlocks' do
context 'without selecting any records' 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)
end

View file

@ -16,7 +16,7 @@ describe 'Admin::IpBlocks' do
context 'without selecting any records' 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)
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
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_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)
end
end

View file

@ -17,7 +17,7 @@ describe 'Admin::Statuses' do
context 'without selecting any records' 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)
end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Links::PreviewCardProviders' do
context 'without selecting any records' 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)
end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Links' do
context 'without selecting any records' 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)
end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Statuses' do
context 'without selecting any records' 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)
end

View file

@ -16,7 +16,7 @@ describe 'Admin::Trends::Tags' do
context 'without selecting any records' 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)
end

View file

@ -23,7 +23,7 @@ describe 'email confirmation flow when captcha is enabled' do
expect(user.reload.confirmed?).to be false
# 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(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
fill_in 'user_email', with: email
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')
end
@ -27,7 +27,7 @@ describe '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_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'))
end
@ -38,7 +38,7 @@ describe 'Log in' do
it 'A unconfirmed user is able to log in' do
fill_in 'user_email', with: email
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')
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'))
# 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)
# 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'))
# 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)
# 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
fill_in 'user_email', with: email
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'))
# Logging in redirects to an authorization page
fill_in 'user_email', with: email
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'))
# 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)
# 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
fill_in 'user_email', with: email
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'))
# Logging in redirects to an authorization page
fill_in 'user_email', with: email
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'))
# 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)
# 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
fill_in 'user_email', with: email
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'))
# Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email
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'))
# Filling in an incorrect two-factor authentication code presents the form again
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'))
# Filling in the correct TOTP code redirects to an app authorization page
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'))
# 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)
# 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
fill_in 'user_email', with: email
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'))
# Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email
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'))
# Filling in an incorrect two-factor authentication code presents the form again
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'))
# Filling in the correct TOTP code redirects to an app authorization page
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'))
# 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)
# 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)
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

View file

@ -184,4 +184,58 @@ describe Mastodon::CLI::Media do
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

View file

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

View file

@ -20,7 +20,7 @@ describe 'Content-Security-Policy' do
"form-action 'self'",
"child-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'"
)
end

View file

@ -94,6 +94,72 @@ describe 'signature verification concern' do
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
it 'fails to verify signature', :aggregate_failures do
get '/activitypub/alternative-path', headers: {

View file

@ -27,6 +27,27 @@ RSpec.describe AppSignUpService, type: :service do
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
before do
Setting.registrations_mode = 'none'

View file

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

View file

@ -24,7 +24,7 @@ describe 'NewStatuses' do
within('.compose-form') do
fill_in "What's on your mind?", with: status_text
click_button 'Publish!'
click_on 'Publish!'
end
expect(subject).to have_css('.status__content__text', text: status_text)
@ -37,7 +37,7 @@ describe 'NewStatuses' do
within('.compose-form') do
fill_in "What's on your mind?", with: status_text
click_button 'Publish!'
click_on 'Publish!'
end
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 url = require('url');
const cors = require('cors');
const dotenv = require('dotenv');
const express = require('express');
const Redis = require('ioredis');
@ -187,6 +188,7 @@ const startServer = async () => {
const pgPool = new pg.Pool(pgConfigFromEnv(process.env));
const server = http.createServer(app);
app.use(cors());
/**
* @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} res
@ -1042,7 +1031,6 @@ const startServer = async () => {
api.use(setRequestId);
api.use(setRemoteAddress);
api.use(allowCrossDomain);
api.use(authenticationMiddleware);
api.use(errorMiddleware);

View file

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

267
yarn.lock
View file

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