Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `Gemfile.lock`:
  Not a real conflict, just a glitch-soc-only dependency too close to a
  dependency that got updated upstream. Updated as well.
- `app/models/status.rb`:
  Not a real conflict, just a change too close to glitch-soc-changed code
  for optionally showing boosts in public timelines.
  Applied upstream changes.
- `app/views/layouts/application.html.haml`:
  Upstream a new, static CSS file, conflict due to glitch-soc's theming
  system, include the file regardless of the theme.
- `config/initializers/content_security_policy.rb`:
  Upstream dropped 'unsafe-inline' from the 'style-src' directive, but
  both files are very different. Removed 'unsafe-inline' as well.
This commit is contained in:
Thibaut Girka
2020-05-10 15:15:39 +02:00
68 changed files with 2287 additions and 1421 deletions

View File

@@ -41,7 +41,7 @@ class AccountsController < ApplicationController
format.rss do
expires_in 1.minute, public: true
@statuses = filtered_statuses.without_reblogs.without_replies.limit(PAGE_SIZE)
@statuses = filtered_statuses.without_reblogs.limit(PAGE_SIZE)
@statuses = cache_collection(@statuses, Status)
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
end
@@ -130,11 +130,11 @@ class AccountsController < ApplicationController
end
def media_requested?
request.path.ends_with?('/media') && !tag_requested?
request.path.split('.').first.ends_with?('/media') && !tag_requested?
end
def replies_requested?
request.path.ends_with?('/with_replies') && !tag_requested?
request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
end
def tag_requested?

View File

@@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
return [] if hide_results?
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end

View File

@@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
return [] if hide_results?
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end

View File

@@ -39,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end
def public_timeline_statuses
Status.as_public_timeline(current_account, truthy_param?(:local))
Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local))
end
def insert_pagination_headers
@@ -47,7 +47,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end
def pagination_params(core_params)
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params)
end
def next_path

View File

@@ -113,6 +113,13 @@ class Auth::SessionsController < Devise::SessionsController
render :two_factor
end
def require_no_authentication
super
# Delete flash message that isn't entirely useful and may be confusing in
# most cases because /web doesn't display/clear flash messages.
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
end
private
def set_pack

View File

@@ -22,8 +22,7 @@ class Settings::IdentityProofsController < Settings::BaseController
if current_account.username.casecmp(params[:username]).zero?
render layout: 'auth'
else
flash[:alert] = I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
redirect_to settings_identity_proofs_path
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
end
end
@@ -35,11 +34,16 @@ class Settings::IdentityProofsController < Settings::BaseController
PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof?
redirect_to @proof.on_success_path(params[:user_agent])
else
flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
redirect_to settings_identity_proofs_path
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
end
end
def destroy
@proof = current_account.identity_proofs.find(params[:id])
@proof.destroy!
redirect_to settings_identity_proofs_path, success: I18n.t('identity_proofs.removed')
end
private
def check_enabled

View File

@@ -7,13 +7,13 @@ module HomeHelper
}
end
def account_link_to(account, button = '', size: 36, path: nil)
def account_link_to(account, button = '', path: nil)
content_tag(:div, class: 'account') do
content_tag(:div, class: 'account__wrapper') do
section = if account.nil?
content_tag(:div, class: 'account__display-name') do
content_tag(:div, class: 'account__avatar-wrapper') do
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})")
image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'account__avatar')
end +
content_tag(:span, class: 'display-name') do
content_tag(:strong, t('about.contact_missing')) +
@@ -23,7 +23,7 @@ module HomeHelper
else
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
content_tag(:div, class: 'account__avatar-wrapper') do
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)})")
image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar')
end +
content_tag(:span, class: 'display-name') do
content_tag(:bdi) do

View File

@@ -68,6 +68,7 @@ module SettingsHelper
tr: 'Türkçe',
uk: 'Українська',
ur: 'اُردُو',
vi: 'Tiếng Việt',
'zh-CN': '简体中文',
'zh-HK': '繁體中文(香港)',
'zh-TW': '繁體中文(臺灣)',

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
module WebfingerHelper
def webfinger!(uri)
hidden_service_uri = /\.(onion|i2p)(:\d+)?$/.match(uri)
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && hidden_service_uri
opts = {
ssl: !hidden_service_uri,
headers: {
'User-Agent': Mastodon::Version.user_agent,
},
}
Goldfinger::Client.new(uri, opts.merge(Rails.configuration.x.http_client_proxy)).finger
end
end

View File

@@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`);
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);

View File

@@ -107,7 +107,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
};
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });

View File

@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import SettingToggle from '../../notifications/components/setting_toggle';
export default @injectIntl
class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
};
render () {
const { settings, onChange } = this.props;
return (
<div>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
<SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} />
</div>
</div>
);
}
}

View File

@@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import ColumnSettings from '../../community_timeline/components/column_settings';
import ColumnSettings from '../components/column_settings';
import { changeSetting } from '../../../actions/settings';
import { changeColumnParams } from '../../../actions/columns';

View File

@@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => {
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
return {
hasUnread: !!timelineState && timelineState.get('unread') > 0,
onlyMedia,
onlyRemote,
};
};
@@ -47,15 +49,16 @@ class PublicTimeline extends React.PureComponent {
multiColumn: PropTypes.bool,
hasUnread: PropTypes.bool,
onlyMedia: PropTypes.bool,
onlyRemote: PropTypes.bool,
};
handlePin = () => {
const { columnId, dispatch, onlyMedia } = this.props;
const { columnId, dispatch, onlyMedia, onlyRemote } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('PUBLIC', { other: { onlyMedia } }));
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } }));
}
}
@@ -69,19 +72,19 @@ class PublicTimeline extends React.PureComponent {
}
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { dispatch, onlyMedia, onlyRemote } = this.props;
dispatch(expandPublicTimeline({ onlyMedia }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
}
componentDidUpdate (prevProps) {
if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
const { dispatch, onlyMedia, onlyRemote } = this.props;
this.disconnect();
dispatch(expandPublicTimeline({ onlyMedia }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
}
}
@@ -97,13 +100,13 @@ class PublicTimeline extends React.PureComponent {
}
handleLoadMore = maxId => {
const { dispatch, onlyMedia } = this.props;
const { dispatch, onlyMedia, onlyRemote } = this.props;
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote }));
}
render () {
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
const pinned = !!columnId;
return (
@@ -122,7 +125,7 @@ class PublicTimeline extends React.PureComponent {
</ColumnHeader>
<StatusListContainer
timelineId={`public${onlyMedia ? ':media' : ''}`}
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore}
trackScroll={!pinned}
scrollKey={`public_timeline-${columnId}`}

View File

@@ -5,30 +5,21 @@ import ColumnHeader from '../column_header';
describe('<Column />', () => {
describe('<ColumnHeader /> click handler', () => {
const originalRaf = global.requestAnimationFrame;
beforeEach(() => {
global.requestAnimationFrame = jest.fn();
});
afterAll(() => {
global.requestAnimationFrame = originalRaf;
});
it('runs the scroll animation if the column contains scrollable content', () => {
const wrapper = mount(
<Column heading='notifications'>
<div className='scrollable' />
</Column>,
);
const scrollToMock = jest.fn();
wrapper.find(Column).find('.scrollable').getDOMNode().scrollTo = scrollToMock;
wrapper.find(ColumnHeader).find('button').simulate('click');
expect(global.requestAnimationFrame.mock.calls.length).toEqual(1);
expect(scrollToMock).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
});
it('does not try to scroll if there is no scrollable content', () => {
const wrapper = mount(<Column heading='notifications' />);
wrapper.find(ColumnHeader).find('button').simulate('click');
expect(global.requestAnimationFrame.mock.calls.length).toEqual(0);
});
});
});

View File

@@ -37,6 +37,7 @@ const componentMap = {
'HOME': HomeTimeline,
'NOTIFICATIONS': Notifications,
'PUBLIC': PublicTimeline,
'REMOTE': PublicTimeline,
'COMMUNITY': CommunityTimeline,
'HASHTAG': HashtagTimeline,
'DIRECT': DirectTimeline,

View File

@@ -543,12 +543,6 @@ $small-breakpoint: 960px;
flex: 0 0 auto;
}
&__avatar {
width: 44px;
height: 44px;
background-size: 44px 44px;
}
.display-name {
font-size: 15px;
@@ -749,12 +743,6 @@ $small-breakpoint: 960px;
display: flex;
align-items: center;
}
.account__avatar {
width: 44px;
height: 44px;
background-size: 44px 44px;
}
}
&__counters__wrapper {

View File

@@ -1318,8 +1318,13 @@
.account__avatar {
@include avatar-radius;
display: block;
position: relative;
width: 36px;
height: 36px;
background-size: 36px 36px;
&-inline {
display: inline-block;
vertical-align: middle;

View File

@@ -93,12 +93,6 @@
display: flex;
align-items: center;
}
.account__avatar {
width: 44px;
height: 44px;
background-size: 44px 44px;
}
}
.trends__item {

View File

@@ -73,8 +73,6 @@ class Request
response.body_with_limit if http_client.persistent?
yield response if block_given?
rescue => e
raise e.class, e.message, e.backtrace[0]
ensure
http_client.close unless http_client.persistent?
end

38
app/lib/rss/serializer.rb Normal file
View File

@@ -0,0 +1,38 @@
# frozen_string_literal: true
class RSS::Serializer
private
def render_statuses(builder, statuses)
statuses.each do |status|
builder.item do |item|
item.title(status_title(status))
.link(ActivityPub::TagManager.instance.url_for(status))
.pub_date(status.created_at)
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
status.media_attachments.each do |media|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
end
end
end
end
def status_title(status)
return "#{status.account.acct} deleted status" if status.destroyed?
preview = status.proper.spoiler_text.presence || status.proper.text
if preview.length > 30 || preview[0, 30].include?("\n")
preview = preview[0, 30]
preview = preview[0, preview.index("\n").presence || 30] + '…'
end
preview = "#{status.proper.spoiler_text.present? ? 'CW ' : ''}#{preview}#{status.proper.sensitive? ? ' (sensitive)' : ''}"
if status.reblog?
"#{status.account.acct} boosted #{status.reblog.account.acct}: #{preview}"
else
"#{status.account.acct}: #{preview}"
end
end
end

View File

@@ -1,13 +1,24 @@
# frozen_string_literal: true
class SidekiqErrorHandler
BACKTRACE_LIMIT = 3
def call(*)
yield
rescue Mastodon::HostValidationError
# Do not retry
rescue => e
limit_backtrace_and_raise(e)
ensure
socket = Thread.current[:statsd_socket]
socket&.close
Thread.current[:statsd_socket] = nil
end
private
def limit_backtrace_and_raise(e)
e.set_backtrace(e.backtrace.first(BACKTRACE_LIMIT))
raise e
end
end

View File

@@ -23,7 +23,7 @@ class RelationshipFilter
scope = scope_for('relationship', params['relationship'].to_s.strip)
params.each do |key, value|
next if key.to_s == 'page'
next if %w(relationship page).include?(key)
scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
end

View File

@@ -3,6 +3,7 @@
class RemoteFollow
include ActiveModel::Validations
include RoutingHelper
include WebfingerHelper
attr_accessor :acct, :addressable_template
@@ -71,7 +72,7 @@ class RemoteFollow
end
def acct_resource
@acct_resource ||= Goldfinger.finger("acct:#{acct}")
@acct_resource ||= webfinger!("acct:#{acct}")
rescue Goldfinger::Error, HTTP::ConnectionError
nil
end

View File

@@ -203,14 +203,6 @@ class Status < ApplicationRecord
preview_cards.first
end
def title
if destroyed?
"#{account.acct} deleted status"
else
reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
end
end
def hidden?
!distributable?
end
@@ -342,7 +334,7 @@ class Status < ApplicationRecord
query = timeline_scope(local_only)
query = query.without_replies unless Setting.show_replies_in_public_timelines
apply_timeline_filters(query, account, local_only)
apply_timeline_filters(query, account, [:local, true].include?(local_only))
end
def as_tag_timeline(tag, account = nil, local_only = false)
@@ -434,8 +426,15 @@ class Status < ApplicationRecord
private
def timeline_scope(local_only = false)
starting_scope = local_only ? Status.local : Status
def timeline_scope(scope = false)
starting_scope = case scope
when :local, true
Status.local
when :remote
Status.remote
else
Status
end
starting_scope = starting_scope.with_public_visibility
if Setting.show_reblogs_in_public_timelines
starting_scope

View File

@@ -4,7 +4,7 @@ class OEmbedSerializer < ActiveModel::Serializer
include RoutingHelper
include ActionView::Helpers::TagHelper
attributes :type, :version, :title, :author_name,
attributes :type, :version, :author_name,
:author_url, :provider_name, :provider_url,
:cache_age, :html, :width, :height

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
class RSS::AccountSerializer
class RSS::AccountSerializer < RSS::Serializer
include ActionView::Helpers::NumberHelper
include AccountsHelper
include RoutingHelper
@@ -17,18 +17,7 @@ class RSS::AccountSerializer
builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar?
builder.cover(full_asset_url(account.header.url(:original))) if account.header?
statuses.each do |status|
builder.item do |item|
item.title(status.title)
.link(ActivityPub::TagManager.instance.url_for(status))
.pub_date(status.created_at)
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
status.media_attachments.each do |media|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
end
end
end
render_statuses(builder, statuses)
builder.to_xml
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
class RSS::TagSerializer
class RSS::TagSerializer < RSS::Serializer
include ActionView::Helpers::NumberHelper
include ActionView::Helpers::SanitizeHelper
include RoutingHelper
@@ -14,18 +14,7 @@ class RSS::TagSerializer
.logo(full_pack_url('media/images/logo.svg'))
.accent_color('2b90d9')
statuses.each do |status|
builder.item do |item|
item.title(status.title)
.link(ActivityPub::TagManager.instance.url_for(status))
.pub_date(status.created_at)
.description(status.spoiler_text.presence || Formatter.instance.format(status).to_str)
status.media_attachments.each do |media|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
end
end
end
render_statuses(builder, statuses)
builder.to_xml
end

View File

@@ -3,6 +3,7 @@
class ActivityPub::FetchRemoteAccountService < BaseService
include JsonLdHelper
include DomainControlHelper
include WebfingerHelper
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
@@ -35,12 +36,12 @@ class ActivityPub::FetchRemoteAccountService < BaseService
private
def verified_webfinger?
webfinger = Goldfinger.finger("acct:#{@username}@#{@domain}")
webfinger = webfinger!("acct:#{@username}@#{@domain}")
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
return webfinger.link('self')&.href == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
webfinger = Goldfinger.finger("acct:#{confirmed_username}@#{confirmed_domain}")
webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
@username, @domain = split_acct(webfinger.subject)
self_reference = webfinger.link('self')

View File

@@ -3,6 +3,7 @@
class ResolveAccountService < BaseService
include JsonLdHelper
include DomainControlHelper
include WebfingerHelper
class WebfingerRedirectError < StandardError; end
@@ -76,7 +77,7 @@ class ResolveAccountService < BaseService
end
def process_webfinger!(uri, redirected = false)
@webfinger = Goldfinger.finger("acct:#{uri}")
@webfinger = webfinger!("acct:#{uri}")
confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?

View File

@@ -25,7 +25,7 @@
- target_account = reports.first.target_account
.report-card
.report-card__profile
= account_link_to target_account, '', size: 36, path: admin_account_path(target_account.id)
= account_link_to target_account, '', path: admin_account_path(target_account.id)
.report-card__profile__stats
= link_to t('admin.reports.account.notes', count: target_account.targeted_moderation_notes.count), admin_account_path(target_account.id)
%br/

View File

@@ -28,6 +28,8 @@
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
= csrf_meta_tags
= stylesheet_link_tag '/inert.css', skip_pipeline: true, media: 'all', id: 'inert-style'
= yield :header_tags
-# These must come after :header_tags to ensure our initial state has been defined.

View File

@@ -18,3 +18,4 @@
%td
= table_link_to 'external-link', t('identity_proofs.view_proof'), proof.badge.proof_url if proof.badge.proof_url
= table_link_to 'trash', t('identity_proofs.remove'), settings_identity_proof_path(proof), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }

View File

@@ -52,13 +52,9 @@ class ActivityPub::DeliveryWorker
end
end
begin
light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD)
.with_cool_off_time(STOPLIGHT_COOLDOWN)
.run
rescue Stoplight::Error::RedLight => e
raise e.class, e.message, e.backtrace.first(3)
end
light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD)
.with_cool_off_time(STOPLIGHT_COOLDOWN)
.run
end
def failure_tracker