Merge branch 'develop' of https://code.gab.com/gab/social/gab-social into develop
This commit is contained in:
@@ -10,7 +10,11 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :set_cache_headers, only: [:edit, :update]
|
||||
prepend_before_action :check_if_password_email_identical, only: [:create]
|
||||
prepend_before_action :check_captcha, only: [:create]
|
||||
if ENV.fetch('GAB_CAPTCHA_CLIENT_KEY', '').empty? || ENV.fetch('GAB_CAPTCHA_CLIENT_KEY', '').nil?
|
||||
# captcha disabled if key not defined
|
||||
else
|
||||
prepend_before_action :check_captcha, only: [:create]
|
||||
end
|
||||
|
||||
def new
|
||||
set_challenge_buster
|
||||
|
||||
@@ -4,123 +4,4 @@
|
||||
# <https://tools.ietf.org/html/draft-cavage-http-signatures-06>
|
||||
module SignatureVerification
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def signed_request?
|
||||
request.headers['Signature'].present?
|
||||
end
|
||||
|
||||
def signature_verification_failure_reason
|
||||
return @signature_verification_failure_reason if defined?(@signature_verification_failure_reason)
|
||||
end
|
||||
|
||||
def signed_request_account
|
||||
return @signed_request_account if defined?(@signed_request_account)
|
||||
|
||||
unless signed_request?
|
||||
@signature_verification_failure_reason = 'Request not signed'
|
||||
@signed_request_account = nil
|
||||
return
|
||||
end
|
||||
|
||||
if request.headers['Date'].present? && !matches_time_window?
|
||||
@signature_verification_failure_reason = 'Signed request date outside acceptable time window'
|
||||
@signed_request_account = nil
|
||||
return
|
||||
end
|
||||
|
||||
raw_signature = request.headers['Signature']
|
||||
signature_params = {}
|
||||
|
||||
raw_signature.split(',').each do |part|
|
||||
parsed_parts = part.match(/([a-z]+)="([^"]+)"/i)
|
||||
next if parsed_parts.nil? || parsed_parts.size != 3
|
||||
signature_params[parsed_parts[1]] = parsed_parts[2]
|
||||
end
|
||||
|
||||
if incompatible_signature?(signature_params)
|
||||
@signature_verification_failure_reason = 'Incompatible request signature'
|
||||
@signed_request_account = nil
|
||||
return
|
||||
end
|
||||
|
||||
account = nil
|
||||
|
||||
if account.nil?
|
||||
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
|
||||
@signed_request_account = nil
|
||||
return
|
||||
end
|
||||
|
||||
signature = Base64.decode64(signature_params['signature'])
|
||||
compare_signed_string = build_signed_string(signature_params['headers'])
|
||||
|
||||
return account unless verify_signature(account, signature, compare_signed_string).nil?
|
||||
|
||||
account = nil
|
||||
|
||||
if account.nil?
|
||||
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
|
||||
@signed_request_account = nil
|
||||
return
|
||||
end
|
||||
|
||||
return account unless verify_signature(account, signature, compare_signed_string).nil?
|
||||
|
||||
# : todo :
|
||||
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
|
||||
@signed_request_account = nil
|
||||
end
|
||||
|
||||
def request_body
|
||||
@request_body ||= request.raw_post
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_signature(account, signature, compare_signed_string)
|
||||
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
|
||||
@signed_request_account = account
|
||||
@signed_request_account
|
||||
end
|
||||
rescue OpenSSL::PKey::RSAError
|
||||
nil
|
||||
end
|
||||
|
||||
def build_signed_string(signed_headers)
|
||||
signed_headers = 'date' if signed_headers.blank?
|
||||
|
||||
signed_headers.downcase.split(' ').map do |signed_header|
|
||||
if signed_header == Request::REQUEST_TARGET
|
||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
||||
elsif signed_header == 'digest'
|
||||
"digest: #{body_digest}"
|
||||
else
|
||||
"#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
|
||||
end
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def matches_time_window?
|
||||
begin
|
||||
time_sent = Time.httpdate(request.headers['Date'])
|
||||
rescue ArgumentError
|
||||
return false
|
||||
end
|
||||
|
||||
(Time.now.utc - time_sent).abs <= 12.hours
|
||||
end
|
||||
|
||||
def body_digest
|
||||
"SHA-256=#{Digest::SHA256.base64digest(request_body)}"
|
||||
end
|
||||
|
||||
def to_header_name(name)
|
||||
name.split(/-/).map(&:capitalize).join('-')
|
||||
end
|
||||
|
||||
def incompatible_signature?(signature_params)
|
||||
signature_params['keyId'].blank? ||
|
||||
signature_params['signature'].blank?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -12,11 +12,12 @@ class CharacterCounter extends React.PureComponent {
|
||||
render() {
|
||||
const { text, max } = this.props
|
||||
|
||||
const actualRadius = 16
|
||||
const radius = 12
|
||||
const actualRadius = 10
|
||||
const radius = 8
|
||||
const circumference = 2 * Math.PI * radius
|
||||
const diff = Math.min(length(text), max) / max
|
||||
const dashoffset = circumference * (1 - diff)
|
||||
const circleClass = length(text) > max ? _s.strokeError : _s.strokeBrand
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.mr10, _s.jcCenter, _s.aiCenter].join(' ')}>
|
||||
@@ -31,8 +32,8 @@ class CharacterCounter extends React.PureComponent {
|
||||
cy={actualRadius}
|
||||
r={radius}
|
||||
fill='none'
|
||||
stroke='#e6e6e6'
|
||||
strokeWidth='2'
|
||||
className={_s.strokeSecondary}
|
||||
/>
|
||||
<circle
|
||||
style={{
|
||||
@@ -43,9 +44,9 @@ class CharacterCounter extends React.PureComponent {
|
||||
cx={actualRadius}
|
||||
cy={actualRadius}
|
||||
r={radius}
|
||||
strokeWidth='2'
|
||||
strokeWidth='2.25'
|
||||
strokeLinecap='round'
|
||||
stroke='#21cf7a'
|
||||
className={circleClass}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import {
|
||||
CX,
|
||||
MODAL_COMPOSE,
|
||||
MAX_POST_CHARACTER_COUNT,
|
||||
POPOVER_COMPOSE_POST_DESTINATION,
|
||||
} from '../../../constants'
|
||||
import { openModal } from '../../../actions/modal'
|
||||
@@ -14,6 +15,7 @@ import Avatar from '../../../components/avatar'
|
||||
import Button from '../../../components/button'
|
||||
import Icon from '../../../components/icon'
|
||||
import Text from '../../../components/text'
|
||||
import CharacterCounter from '../../../components/character_counter'
|
||||
|
||||
class ComposeDestinationHeader extends ImmutablePureComponent {
|
||||
|
||||
@@ -38,6 +40,7 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
|
||||
composeGroup,
|
||||
composeGroupId,
|
||||
formLocation,
|
||||
text,
|
||||
} = this.props
|
||||
|
||||
const isIntroduction = formLocation === 'introduction'
|
||||
@@ -85,6 +88,10 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!!text &&
|
||||
<CharacterCounter max={MAX_POST_CHARACTER_COUNT} text={text} />
|
||||
}
|
||||
{
|
||||
!isModal && !isIntroduction &&
|
||||
<Button
|
||||
@@ -94,6 +101,7 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
|
||||
color='tertiary'
|
||||
icon='fullscreen'
|
||||
onClick={this.handleOnExpand}
|
||||
className={_s.ml10}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
@@ -106,6 +114,7 @@ const mapStateToProps = (state) => {
|
||||
|
||||
return {
|
||||
composeGroupId,
|
||||
text: state.getIn(['compose', 'text']),
|
||||
isReply: !!state.getIn(['compose', 'in_reply_to']),
|
||||
isEdit: state.getIn(['compose', 'id']) !== null,
|
||||
composeGroup: state.getIn(['groups', composeGroupId]),
|
||||
|
||||
@@ -54,7 +54,6 @@ class Followers extends ImmutablePureComponent {
|
||||
scrollKey='followers'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
placeholderComponent={AccountPlaceholder}
|
||||
placeholderCount={4}
|
||||
|
||||
@@ -54,7 +54,6 @@ class Following extends ImmutablePureComponent {
|
||||
scrollKey='following'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
placeholderComponent={AccountPlaceholder}
|
||||
placeholderCount={4}
|
||||
|
||||
@@ -1110,6 +1110,10 @@ pre {
|
||||
fill: var(--color_primary);
|
||||
}
|
||||
|
||||
.fillSecondary {
|
||||
fill: var(--text_color_secondary);
|
||||
}
|
||||
|
||||
.fillWhite {
|
||||
fill: var(--color_white);
|
||||
}
|
||||
@@ -1133,6 +1137,10 @@ pre {
|
||||
color: var(--navigation_brand);
|
||||
}
|
||||
|
||||
.strokeBrand { stroke: var(--color_brand); }
|
||||
.strokeSecondary { stroke: var(--solid_color_secondary); }
|
||||
.strokeError { stroke: var(--color_red); }
|
||||
|
||||
.navigationUnderlineActive:after {
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
# username :string default(""), not null
|
||||
# domain :string
|
||||
# secret :string default(""), not null
|
||||
# private_key :text
|
||||
# public_key :text default(""), not null
|
||||
# remote_url :string default(""), not null
|
||||
# salmon_url :string default(""), not null
|
||||
# hub_url :string default(""), not null
|
||||
@@ -54,6 +52,8 @@
|
||||
#
|
||||
|
||||
class Account < ApplicationRecord
|
||||
self.ignored_columns = ["private_key"]
|
||||
self.ignored_columns = ["public_key"]
|
||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
|
||||
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
|
||||
MIN_FOLLOWERS_DISCOVERY = 10
|
||||
@@ -200,10 +200,6 @@ class Account < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def keypair
|
||||
@keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
|
||||
end
|
||||
|
||||
def tags_as_strings=(tag_names)
|
||||
tag_names.map! { |name| name.mb_chars.downcase.to_s }
|
||||
tag_names.uniq!
|
||||
@@ -279,21 +275,6 @@ class Account < ApplicationRecord
|
||||
self.fields = tmp
|
||||
end
|
||||
|
||||
def magic_key
|
||||
modulus, exponent = [keypair.public_key.n, keypair.public_key.e].map do |component|
|
||||
result = []
|
||||
|
||||
until component.zero?
|
||||
result << [component % 256].pack('C')
|
||||
component >>= 8
|
||||
end
|
||||
|
||||
result.reverse.join
|
||||
end
|
||||
|
||||
(['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
|
||||
end
|
||||
|
||||
def save_with_optional_media!
|
||||
save!
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
@@ -444,7 +425,6 @@ class Account < ApplicationRecord
|
||||
@emojis ||= CustomEmoji.from_text(emojifiable_text)
|
||||
end
|
||||
|
||||
before_create :generate_keys
|
||||
before_validation :prepare_contents, if: :local?
|
||||
before_validation :prepare_username, on: :create
|
||||
before_destroy :clean_feed_manager
|
||||
@@ -460,14 +440,6 @@ class Account < ApplicationRecord
|
||||
username&.squish!
|
||||
end
|
||||
|
||||
def generate_keys
|
||||
return unless local? && !Rails.env.test?
|
||||
|
||||
keypair = OpenSSL::PKey::RSA.new(2048)
|
||||
self.private_key = keypair.to_pem
|
||||
self.public_key = keypair.public_key.to_pem
|
||||
end
|
||||
|
||||
def normalize_domain
|
||||
return if local?
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
# member_count :integer default(0)
|
||||
# slug :text
|
||||
# is_private :boolean default(FALSE)
|
||||
# is_visible :boolean default(FALSE)
|
||||
# is_visible :boolean default(TRUE)
|
||||
# tags :string default([]), is an Array
|
||||
# password :string
|
||||
# group_category_id :integer
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# id :bigint(8) not null, primary key
|
||||
# list_id :bigint(8) not null
|
||||
# account_id :bigint(8) not null
|
||||
# follow_id :bigint(8) default(1)
|
||||
# follow_id :bigint(8)
|
||||
#
|
||||
|
||||
class ListAccount < ApplicationRecord
|
||||
|
||||
Reference in New Issue
Block a user