Browse Source

Merge Upstream

master
Tuan 3 months ago
parent
commit
ef9cb2468d
  1. 42
      .github/ISSUE_TEMPLATE/1.bug_report.yml
  2. 21
      .github/ISSUE_TEMPLATE/2.feature_request.yml
  3. 2
      .github/ISSUE_TEMPLATE/3.support.md
  4. 12
      .github/ISSUE_TEMPLATE/bug_report.md
  5. 16
      .github/ISSUE_TEMPLATE/feature_request.md
  6. 2
      .ruby-version
  7. 2
      Dockerfile
  8. 12
      Gemfile
  9. 138
      Gemfile.lock
  10. 10
      app/controllers/activitypub/outboxes_controller.rb
  11. 4
      app/controllers/admin/resets_controller.rb
  12. 27
      app/controllers/admin/sign_in_token_authentications_controller.rb
  13. 2
      app/controllers/admin/two_factor_authentications_controller.rb
  14. 9
      app/controllers/auth/omniauth_callbacks_controller.rb
  15. 44
      app/controllers/auth/sessions_controller.rb
  16. 5
      app/controllers/concerns/sign_in_token_authentication_concern.rb
  17. 10
      app/controllers/concerns/two_factor_authentication_concern.rb
  18. 2
      app/controllers/follower_accounts_controller.rb
  19. 2
      app/controllers/following_accounts_controller.rb
  20. 13
      app/controllers/settings/login_activities_controller.rb
  21. 3
      app/controllers/well_known/webfinger_controller.rb
  22. 6
      app/helpers/accounts_helper.rb
  23. 11
      app/helpers/application_helper.rb
  24. 21
      app/javascript/flavours/glitch/actions/picture_in_picture.js
  25. 4
      app/javascript/flavours/glitch/components/status_content.js
  26. 16
      app/javascript/flavours/glitch/features/compose/components/search_results.js
  27. 7
      app/javascript/flavours/glitch/features/ui/components/link_footer.js
  28. 3
      app/javascript/flavours/glitch/reducers/picture_in_picture.js
  29. 12
      app/javascript/flavours/glitch/styles/components/boost.scss
  30. 78
      app/javascript/flavours/glitch/styles/components/drawer.scss
  31. 7
      app/javascript/flavours/glitch/styles/components/media.scss
  32. 11
      app/javascript/flavours/glitch/styles/components/search.scss
  33. 1
      app/javascript/flavours/glitch/styles/components/status.scss
  34. 18
      app/javascript/flavours/glitch/styles/forms.scss
  35. 1
      app/javascript/flavours/glitch/util/initial_state.js
  36. 21
      app/javascript/mastodon/actions/picture_in_picture.js
  37. 7
      app/javascript/mastodon/features/ui/components/link_footer.js
  38. 1
      app/javascript/mastodon/initial_state.js
  39. 3
      app/javascript/mastodon/reducers/picture_in_picture.js
  40. 4
      app/javascript/skins/glitch/metulayf/variables.scss
  41. 12
      app/javascript/styles/mastodon/boost.scss
  42. 8
      app/javascript/styles/mastodon/components.scss
  43. 18
      app/javascript/styles/mastodon/forms.scss
  44. 6
      app/models/account.rb
  45. 1
      app/models/account_stat.rb
  46. 2
      app/models/concerns/ldap_authenticable.rb
  47. 35
      app/models/login_activity.rb
  48. 14
      app/models/report_filter.rb
  49. 25
      app/models/user.rb
  50. 8
      app/policies/user_policy.rb
  51. 1
      app/serializers/initial_state_serializer.rb
  52. 29
      app/serializers/rest/instance_serializer.rb
  53. 3
      app/validators/status_length_validator.rb
  54. 4
      app/views/about/more.html.haml
  55. 4
      app/views/about/show.html.haml
  56. 10
      app/views/accounts/_header.html.haml
  57. 2
      app/views/accounts/show.html.haml
  58. 24
      app/views/admin/accounts/show.html.haml
  59. 16
      app/views/admin/dashboard/index.html.haml
  60. 4
      app/views/admin/follow_recommendations/_account.html.haml
  61. 2
      app/views/admin/instances/_instance.html.haml
  62. 6
      app/views/admin/reports/index.html.haml
  63. 2
      app/views/admin/tags/_tag.html.haml
  64. 5
      app/views/auth/registrations/_sessions.html.haml
  65. 4
      app/views/directories/index.html.haml
  66. 3
      app/views/layouts/public.html.haml
  67. 4
      app/views/relationships/_account.html.haml
  68. 2
      app/views/settings/featured_tags/index.html.haml
  69. 17
      app/views/settings/login_activities/_login_activity.html.haml
  70. 15
      app/views/settings/login_activities/index.html.haml
  71. 6
      app/views/statuses/_detailed_status.html.haml
  72. 16
      app/workers/move_worker.rb
  73. 1
      app/workers/scheduler/ip_cleanup_scheduler.rb
  74. 57
      config/locales/en.yml
  75. 2
      config/locales/tr.yml
  76. 2
      config/navigation.rb
  77. 4
      config/routes.rb
  78. 2
      config/webpack/production.js
  79. 2
      db/migrate/20210416200740_create_canonical_email_blocks.rb
  80. 14
      db/migrate/20210609202149_create_login_activities.rb
  81. 5
      db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb
  82. 13
      db/migrate/20210630000137_fix_canonical_email_blocks_foreign_key.rb
  83. 18
      db/schema.rb
  84. 1
      dist/mastodon-sidekiq.service
  85. 1
      dist/mastodon-web.service
  86. 2
      docker-compose.yml
  87. 15
      lib/mastodon/accounts_cli.rb
  88. 13
      lib/mastodon/domains_cli.rb
  89. 26
      package.json
  90. 16
      spec/controllers/activitypub/outboxes_controller_spec.rb
  91. 2
      spec/controllers/admin/resets_controller_spec.rb
  92. 8
      spec/controllers/admin/two_factor_authentications_controller_spec.rb
  93. 19
      spec/controllers/follower_accounts_controller_spec.rb
  94. 19
      spec/controllers/following_accounts_controller_spec.rb
  95. 4
      spec/controllers/well_known/webfinger_controller_spec.rb
  96. 8
      spec/fabricators/login_activity_fabricator.rb
  97. 5
      spec/models/login_activity_spec.rb
  98. 2
      spec/models/tag_feed_spec.rb
  99. 28
      spec/models/user_spec.rb
  100. 33
      spec/services/bootstrap_timeline_service_spec.rb

42
.github/ISSUE_TEMPLATE/1.bug_report.yml

@ -0,0 +1,42 @@
name: Bug Report
description: If something isn't working as expected
labels: bug
body:
- type: markdown
attributes:
value: |
Make sure that you are submitting a new bug that was not previously reported or already fixed.
Please use a concise and distinct title for the issue.
- type: input
attributes:
label: Expected behaviour
description: What should have happened?
validations:
required: true
- type: input
attributes:
label: Actual behaviour
description: What happened?
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce the problem
description: What were you trying to do?
value: |
1.
2.
3.
...
validations:
required: true
- type: textarea
attributes:
label: Specifications
description: |
What version or commit hash of Mastodon did you find this bug in?
If a front-end issue, what browser and operating systems were you using?
validations:
required: true

21
.github/ISSUE_TEMPLATE/2.feature_request.yml

@ -0,0 +1,21 @@
name: Feature Request
description: I have a suggestion
body:
- type: markdown
attributes:
value: |
Please use a concise and distinct title for the issue.
Consider: Could it be implemented as a 3rd party app using the REST API instead?
- type: textarea
attributes:
label: Pitch
description: Describe your idea for a feature. Make sure it has not already been suggested/implemented/turned down before.
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: Why do you think this feature is needed? Who would benefit from it?
validations:
required: true

2
.github/ISSUE_TEMPLATE/support.md → .github/ISSUE_TEMPLATE/3.support.md

@ -1,7 +1,7 @@
---
name: Support
about: Ask for help with your deployment
title: DO NOT CREATE THIS ISSUE
---
We primarily use GitHub as a bug and feature tracker. For usage questions, troubleshooting of deployments and other individual technical assistance, please use one of the resources below:

12
.github/ISSUE_TEMPLATE/bug_report.md

@ -1,12 +0,0 @@
---
name: Bug Report
about: If something isn't working as expected
labels: bug
---
[Issue text goes here].
* * * *
- [ ] I searched or browsed the repo’s other issues to ensure this is not a duplicate.
- [ ] This bugs also occur on vanilla Mastodon

16
.github/ISSUE_TEMPLATE/feature_request.md

@ -1,16 +0,0 @@
---
name: Feature Request
about: I have a suggestion
---
<!-- Please use a concise and distinct title for the issue -->
<!-- Consider: Could it be implemented as a 3rd party app using the REST API instead? -->
### Pitch
<!-- Describe your idea for a feature. Make sure it has not already been suggested/implemented/turned down before -->
### Motivation
<!-- Why do you think this feature is needed? Who would benefit from it? -->

2
.ruby-version

@ -1 +1 @@
2.7.2
2.7.4

2
Dockerfile

@ -26,7 +26,7 @@ RUN ARCH= && \
mv node-v$NODE_VER-linux-$ARCH /opt/node
# Install Ruby
ENV RUBY_VER="2.7.2"
ENV RUBY_VER="2.7.4"
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential \
bison libyaml-dev libgdbm-dev libreadline-dev libjemalloc-dev \

12
Gemfile

@ -6,7 +6,7 @@ ruby '>= 2.5.0', '< 3.1.0'
gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 5.3'
gem 'rails', '~> 6.1.3'
gem 'rails', '~> 6.1.4'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.1'
gem 'rack', '~> 2.2.3'
@ -24,7 +24,7 @@ gem 'paperclip', '~> 6.0'
gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.7'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.6.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
@ -81,7 +81,7 @@ gem 'sanitize', '~> 5.2'
gem 'scenic', '~> 1.5'
gem 'sidekiq', '~> 6.2'
gem 'sidekiq-scheduler', '~> 3.1'
gem 'sidekiq-unique-jobs', '~> 7.0'
gem 'sidekiq-unique-jobs', '~> 7.1'
gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.3'
gem 'simple_form', '~> 5.1'
@ -136,8 +136,8 @@ group :development do
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler'
gem 'rubocop', '~> 1.16', require: false
gem 'rubocop-rails', '~> 2.10', require: false
gem 'rubocop', '~> 1.18', require: false
gem 'rubocop-rails', '~> 2.11', require: false
gem 'brakeman', '~> 5.0', require: false
gem 'bundler-audit', '~> 0.8', require: false
@ -157,5 +157,3 @@ gem 'concurrent-ruby', require: false
gem 'connection_pool', require: false
gem 'xorcist', '~> 1.1'
gem 'resolv', '~> 0.1.0'

138
Gemfile.lock

@ -1,40 +1,40 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
actioncable (6.1.4)
actionpack (= 6.1.4)
activesupport (= 6.1.4)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
actionmailbox (6.1.4)
actionpack (= 6.1.4)
activejob (= 6.1.4)
activerecord (= 6.1.4)
activestorage (= 6.1.4)
activesupport (= 6.1.4)
mail (>= 2.7.1)
actionmailer (6.1.3.2)
actionpack (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activesupport (= 6.1.3.2)
actionmailer (6.1.4)
actionpack (= 6.1.4)
actionview (= 6.1.4)
activejob (= 6.1.4)
activesupport (= 6.1.4)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.3.2)
actionview (= 6.1.3.2)
activesupport (= 6.1.3.2)
actionpack (6.1.4)
actionview (= 6.1.4)
activesupport (= 6.1.4)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.3.2)
actionpack (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
actiontext (6.1.4)
actionpack (= 6.1.4)
activerecord (= 6.1.4)
activestorage (= 6.1.4)
activesupport (= 6.1.4)
nokogiri (>= 1.8.5)
actionview (6.1.3.2)
activesupport (= 6.1.3.2)
actionview (6.1.4)
activesupport (= 6.1.4)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -45,28 +45,28 @@ GEM
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.8)
activejob (6.1.3.2)
activesupport (= 6.1.3.2)
activejob (6.1.4)
activesupport (= 6.1.4)
globalid (>= 0.3.6)
activemodel (6.1.3.2)
activesupport (= 6.1.3.2)
activerecord (6.1.3.2)
activemodel (= 6.1.3.2)
activesupport (= 6.1.3.2)
activestorage (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activesupport (= 6.1.3.2)
activemodel (6.1.4)
activesupport (= 6.1.4)
activerecord (6.1.4)
activemodel (= 6.1.4)
activesupport (= 6.1.4)
activestorage (6.1.4)
actionpack (= 6.1.4)
activejob (= 6.1.4)
activerecord (= 6.1.4)
activesupport (= 6.1.4)
marcel (~> 1.0.0)
mini_mime (~> 1.0.2)
activesupport (6.1.3.2)
mini_mime (>= 1.1.0)
activesupport (6.1.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.7.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
airbrussh (1.4.0)
sshkit (>= 1.6.1, != 1.7.0)
@ -99,7 +99,7 @@ GEM
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
bindata (2.4.8)
bindata (2.4.10)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
blurhash (0.1.5)
@ -353,7 +353,7 @@ GEM
mimemagic (0.3.10)
nokogiri (~> 1)
rake
mini_mime (1.0.3)
mini_mime (1.1.0)
mini_portile2 (2.5.3)
minitest (5.14.4)
msgpack (1.4.2)
@ -374,7 +374,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.11.5)
oj (3.11.8)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
@ -443,20 +443,20 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.1.3.2)
actioncable (= 6.1.3.2)
actionmailbox (= 6.1.3.2)
actionmailer (= 6.1.3.2)
actionpack (= 6.1.3.2)
actiontext (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activemodel (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
rails (6.1.4)
actioncable (= 6.1.4)
actionmailbox (= 6.1.4)
actionmailer (= 6.1.4)
actionpack (= 6.1.4)
actiontext (= 6.1.4)
actionview (= 6.1.4)
activejob (= 6.1.4)
activemodel (= 6.1.4)
activerecord (= 6.1.4)
activestorage (= 6.1.4)
activesupport (= 6.1.4)
bundler (>= 1.15.0)
railties (= 6.1.3.2)
railties (= 6.1.4)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -472,11 +472,11 @@ GEM
railties (>= 6.0.0, < 7)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
railties (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
railties (6.1.4)
actionpack (= 6.1.4)
activesupport (= 6.1.4)
method_source
rake (>= 0.8.7)
rake (>= 0.13)
thor (~> 1.0)
rainbow (3.0.0)
rake (13.0.3)
@ -492,7 +492,6 @@ GEM
regexp_parser (2.1.1)
request_store (1.5.0)
rack (>= 1.4)
resolv (0.1.0)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
@ -525,7 +524,7 @@ GEM
rspec-support (3.10.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.16.1)
rubocop (1.18.3)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
@ -536,7 +535,7 @@ GEM
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.7.0)
parser (>= 3.0.1.1)
rubocop-rails (2.10.1)
rubocop-rails (2.11.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
@ -570,7 +569,7 @@ GEM
sidekiq (>= 3)
thwait
tilt (>= 1.4.0)
sidekiq-unique-jobs (7.0.12)
sidekiq-unique-jobs (7.1.2)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 5.0, < 7.0)
@ -659,7 +658,7 @@ GEM
webpush (0.3.8)
hkdf (~> 0.2)
jwt (~> 2.0)
websocket-driver (0.7.3)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
wisper (2.0.1)
@ -674,7 +673,7 @@ PLATFORMS
DEPENDENCIES
active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.8)
addressable (~> 2.7)
addressable (~> 2.8)
annotate (~> 3.1)
aws-sdk-s3 (~> 1.96)
better_errors (~> 2.9)
@ -758,7 +757,7 @@ DEPENDENCIES
rack (~> 2.2.3)
rack-attack (~> 6.5)
rack-cors (~> 1.1)
rails (~> 6.1.3)
rails (~> 6.1.4)
rails-controller-testing (~> 1.0)
rails-i18n (~> 6.0)
rails-settings-cached (~> 0.6)
@ -766,20 +765,19 @@ DEPENDENCIES
redcarpet (~> 3.5)
redis (~> 4.3)
redis-namespace (~> 1.8)
resolv (~> 0.1.0)
rqrcode (~> 2.0)
rspec-rails (~> 5.0)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.4)
rubocop (~> 1.16)
rubocop-rails (~> 2.10)
rubocop (~> 1.18)
rubocop-rails (~> 2.11)
ruby-progressbar (~> 1.11)
sanitize (~> 5.2)
scenic (~> 1.5)
sidekiq (~> 6.2)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.1)
sidekiq-unique-jobs (~> 7.0)
sidekiq-unique-jobs (~> 7.1)
simple-navigation (~> 4.3)
simple_form (~> 5.1)
simplecov (~> 0.21)

10
app/controllers/activitypub/outboxes_controller.rb

@ -11,7 +11,11 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
before_action :set_cache_headers
def show
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
if page_requested?
expires_in(1.minute, public: public_fetch_mode? && signed_request_account.nil?)
else
expires_in(3.minutes, public: public_fetch_mode?)
end
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
@ -76,4 +80,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_account
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
end
def set_cache_headers
response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested?
end
end

4
app/controllers/admin/resets_controller.rb

@ -6,9 +6,9 @@ module Admin
def create
authorize @user, :reset_password?
@user.send_reset_password_instructions
@user.reset_password!
log_action :reset_password, @user
redirect_to admin_accounts_path
redirect_to admin_account_path(@user.account_id)
end
end
end

27
app/controllers/admin/sign_in_token_authentications_controller.rb

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Admin
class SignInTokenAuthenticationsController < BaseController
before_action :set_target_user
def create
authorize @user, :enable_sign_in_token_auth?
@user.update(skip_sign_in_token: false)
log_action :enable_sign_in_token_auth, @user
redirect_to admin_account_path(@user.account_id)
end
def destroy
authorize @user, :disable_sign_in_token_auth?
@user.update(skip_sign_in_token: true)
log_action :disable_sign_in_token_auth, @user
redirect_to admin_account_path(@user.account_id)
end
private
def set_target_user
@user = User.find(params[:user_id])
end
end
end

2
app/controllers/admin/two_factor_authentications_controller.rb

@ -9,7 +9,7 @@ module Admin
@user.disable_two_factor!
log_action :disable_2fa, @user
UserMailer.two_factor_disabled(@user).deliver_later!
redirect_to admin_accounts_path
redirect_to admin_account_path(@user.account_id)
end
private

9
app/controllers/auth/omniauth_callbacks_controller.rb

@ -10,6 +10,15 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
if @user.persisted?
LoginActivity.create(
user: user,
success: true,
authentication_method: :omniauth,
provider: provider,
ip: request.remote_ip,
user_agent: request.user_agent
)
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: provider_id.capitalize) if is_navigational_format?
else

44
app/controllers/auth/sessions_controller.rb

@ -27,9 +27,11 @@ class Auth::SessionsController < Devise::SessionsController
def create
super do |resource|
resource.update_sign_in!(request, new_sign_in: true)
remember_me(resource)
flash.delete(:notice)
# We only need to call this if this hasn't already been
# called from one of the two-factor or sign-in token
# authentication methods
on_authentication_success(resource, :password) unless @on_authentication_success_called
end
end
@ -44,10 +46,8 @@ class Auth::SessionsController < Devise::SessionsController
def webauthn_options
user = find_user
if user.webauthn_enabled?
options_for_get = WebAuthn::Credential.options_for_get(
allow: user.webauthn_credentials.pluck(:external_id)
)
if user&.webauthn_enabled?
options_for_get = WebAuthn::Credential.options_for_get(allow: user.webauthn_credentials.pluck(:external_id))
session[:webauthn_challenge] = options_for_get.challenge
@ -142,4 +142,34 @@ class Auth::SessionsController < Devise::SessionsController
session.delete(:attempt_user_id)
session.delete(:attempt_user_updated_at)
end
def on_authentication_success(user, security_measure)
@on_authentication_success_called = true
clear_attempt_from_session
user.update_sign_in!(request, new_sign_in: true)
remember_me(user)
sign_in(user)
flash.delete(:notice)
LoginActivity.create(
user: user,
success: true,
authentication_method: security_measure,
ip: request.remote_ip,
user_agent: request.user_agent
)
end
def on_authentication_failure(user, security_measure, failure_reason)
LoginActivity.create(
user: user,
success: false,
authentication_method: security_measure,
failure_reason: failure_reason,
ip: request.remote_ip,
user_agent: request.user_agent
)
end
end

5
app/controllers/concerns/sign_in_token_authentication_concern.rb

@ -29,10 +29,9 @@ module SignInTokenAuthenticationConcern
def authenticate_with_sign_in_token_attempt(user)
if valid_sign_in_token_attempt?(user)
clear_attempt_from_session
remember_me(user)
sign_in(user)
on_authentication_success(user, :sign_in_token)
else
on_authentication_failure(user, :sign_in_token, :invalid_sign_in_token)
flash.now[:alert] = I18n.t('users.invalid_sign_in_token')
prompt_for_sign_in_token(user)
end

10
app/controllers/concerns/two_factor_authentication_concern.rb

@ -52,21 +52,19 @@ module TwoFactorAuthenticationConcern
webauthn_credential = WebAuthn::Credential.from_get(user_params[:credential])
if valid_webauthn_credential?(user, webauthn_credential)
clear_attempt_from_session
remember_me(user)
sign_in(user)
on_authentication_success(user, :webauthn)
render json: { redirect_path: root_path }, status: :ok
else
on_authentication_failure(user, :webauthn, :invalid_credential)
render json: { error: t('webauthn_credentials.invalid_credential') }, status: :unprocessable_entity
end
end
def authenticate_with_two_factor_via_otp(user)
if valid_otp_attempt?(user)
clear_attempt_from_session
remember_me(user)
sign_in(user)
on_authentication_success(user, :otp)
else
on_authentication_failure(user, :otp, :invalid_otp_token)
flash.now[:alert] = I18n.t('users.invalid_otp_token')
prompt_for_two_factor(user)
end

2
app/controllers/follower_accounts_controller.rb

@ -86,7 +86,7 @@ class FollowerAccountsController < ApplicationController
if page_requested? || !@account.user_hides_network?
# Return all fields
else
%i(id type totalItems)
%i(id type total_items)
end
end
end

2
app/controllers/following_accounts_controller.rb

@ -86,7 +86,7 @@ class FollowingAccountsController < ApplicationController
if page_requested? || !@account.user_hides_network?
# Return all fields
else
%i(id type totalItems)
%i(id type total_items)
end
end
end

13
app/controllers/settings/login_activities_controller.rb

@ -0,0 +1,13 @@
# frozen_string_literal: true
class Settings::LoginActivitiesController < Settings::BaseController
def index
@login_activities = LoginActivity.where(user: current_user).order(id: :desc).page(params[:page])
end
private
def set_pack
use_pack 'settings'
end
end

3
app/controllers/well_known/webfinger_controller.rb

@ -4,7 +4,6 @@ module WellKnown
class WebfingerController < ActionController::Base
include RoutingHelper
before_action { response.headers['Vary'] = 'Accept' }
before_action :set_account
before_action :check_account_suspension
@ -39,10 +38,12 @@ module WellKnown
end
def bad_request
expires_in(3.minutes, public: true)
head 400
end
def not_found
expires_in(3.minutes, public: true)
head 404
end

6
app/helpers/accounts_helper.rb

@ -84,19 +84,19 @@ module AccountsHelper
def account_description(account)
prepend_stats = [
[
number_to_human(account.statuses_count, strip_insignificant_zeros: true),
number_to_human(account.statuses_count, precision: 3, strip_insignificant_zeros: true),
I18n.t('accounts.posts', count: account.statuses_count),
].join(' '),
[
number_to_human(account.following_count, strip_insignificant_zeros: true),
number_to_human(account.following_count, precision: 3, strip_insignificant_zeros: true),
I18n.t('accounts.following', count: account.following_count),
].join(' '),
]
unless hide_followers_count?(account)
prepend_stats << [
number_to_human(account.followers_count, strip_insignificant_zeros: true),
number_to_human(account.followers_count, precision: 3, strip_insignificant_zeros: true),
I18n.t('accounts.followers', count: account.followers_count),
].join(' ')
end

11
app/helpers/application_helper.rb

@ -14,6 +14,17 @@ module ApplicationHelper
ku
).freeze
def friendly_number_to_human(number, **options)
# By default, the number of precision digits used by number_to_human
# is looked up from the locales definition, and rails-i18n comes with
# values that don't seem to make much sense for many languages, so
# override these values with a default of 3 digits of precision.
options[:precision] = 3
options[:strip_insignificant_zeros] = true
number_to_human(number, **options)
end
def active_nav_class(*paths)
paths.any? { |path| current_page?(path) } ? 'active' : ''
end

21
app/javascript/flavours/glitch/actions/picture_in_picture.js

@ -22,13 +22,20 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
* @param {MediaProps} props
* @return {object}
*/
export const deployPictureInPicture = (statusId, accountId, playerType, props) => ({
type: PICTURE_IN_PICTURE_DEPLOY,
statusId,
accountId,
playerType,
props,
});
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
return (dispatch, getState) => {
// Do not open a player for a toot that does not exist
if (getState().hasIn(['statuses', statusId])) {
dispatch({
type: PICTURE_IN_PICTURE_DEPLOY,
statusId,
accountId,
playerType,
props,
});
}
};
};
/*
* @return {object}

4
app/javascript/flavours/glitch/components/status_content.js

@ -224,8 +224,8 @@ export default class StatusContent extends React.PureComponent {
const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
let element = e.target;
while (element) {
if (['button', 'video', 'a', 'label', 'canvas'].includes(element.localName)) {
while (element !== e.currentTarget) {
if (['button', 'video', 'a', 'label', 'canvas'].includes(element.localName) || element.getAttribute('role') === 'button') {
return;
}
element = element.parentNode;

16
app/javascript/flavours/glitch/features/compose/components/search_results.js

@ -71,7 +71,7 @@ class SearchResults extends ImmutablePureComponent {
);
} else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
statuses = (
<section>
<section className='search-results__section'>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
<div className='search-results__info'>
@ -87,7 +87,7 @@ class SearchResults extends ImmutablePureComponent {
if (results.get('accounts') && results.get('accounts').size > 0) {
count += results.get('accounts').size;
accounts = (
<section>
<section className='search-results__section'>
<h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
{results.get('accounts').map(accountId => <AccountContainer id={accountId} key={accountId} />)}
@ -100,7 +100,7 @@ class SearchResults extends ImmutablePureComponent {
if (results.get('statuses') && results.get('statuses').size > 0) {
count += results.get('statuses').size;
statuses = (
<section>
<section className='search-results__section'>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
{results.get('statuses').map(statusId => <StatusContainer id={statusId} key={statusId}/>)}
@ -113,7 +113,7 @@ class SearchResults extends ImmutablePureComponent {
if (results.get('hashtags') && results.get('hashtags').size > 0) {
count += results.get('hashtags').size;
hashtags = (
<section>
<section className='search-results__section'>
<h5><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
{results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
@ -131,11 +131,9 @@ class SearchResults extends ImmutablePureComponent {
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
</header>
<div className='search-results__contents'>
{accounts}
{statuses}
{hashtags}
</div>
{accounts}
{statuses}
{hashtags}
</div>
);
};

7
app/javascript/flavours/glitch/features/ui/components/link_footer.js

@ -3,7 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { invitesEnabled, version, repository, source_url } from 'flavours/glitch/util/initial_state';
import { invitesEnabled, version, limitedFederationMode, repository, source_url } from 'flavours/glitch/util/initial_state';
import { signOutLink, securityLink } from 'flavours/glitch/util/backend_links';
import { logOut } from 'flavours/glitch/util/log_out';
import { openModal } from 'flavours/glitch/actions/modal';
@ -45,8 +45,9 @@ class LinkFooter extends React.PureComponent {
return (
<div className='getting-started__footer'>
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite hocams' /></a> · </li>}
{!!securityLink && <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>}
{!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>}
<li><a href='/apps/index.html' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href={signOutLink} onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>

3
app/javascript/flavours/glitch/reducers/picture_in_picture.js

@ -1,4 +1,5 @@
import { PICTURE_IN_PICTURE_DEPLOY, PICTURE_IN_PICTURE_REMOVE } from 'flavours/glitch/actions/picture_in_picture';
import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines';
const initialState = {
statusId: null,
@ -16,6 +17,8 @@ export default function pictureInPicture(state = initialState, action) {
return { statusId: action.statusId, accountId: action.accountId, type: action.playerType, ...action.props };
case PICTURE_IN_PICTURE_REMOVE:
return { ...initialState };
case TIMELINE_DELETE:
return (state.statusId === action.id) ? { ...initialState } : state;
default:
return state;
}

12
app/javascript/flavours/glitch/styles/components/boost.scss
File diff suppressed because it is too large
View File

78
app/javascript/flavours/glitch/styles/components/drawer.scss

@ -120,20 +120,22 @@
}
.drawer--results {
background: $ui-base-color;
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow-x: hidden;
overflow-y: scroll;
}
& > header {
color: $dark-text-color;
background: lighten($ui-base-color, 2%);
.search-results__section {
margin-bottom: 5px;
h5 {
background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
cursor: default;
display: flex;
padding: 15px;
font-weight: 500;
font-size: 16px;
cursor: default;
flex: 0 0 auto;
color: $dark-text-color;
.fa {
display: inline-block;
@ -141,48 +143,22 @@
}
}
& > .search-results__contents {
overflow-x: hidden;
overflow-y: scroll;
flex: 1 1 auto;
.account:last-child,
& > div:last-child .status {
border-bottom: 0;
}
& > .hashtag {
display: block;
padding: 10px;
color: $secondary-text-color;
text-decoration: none;
& > section {
margin-bottom: 5px;
h5 {
background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
cursor: default;
display: flex;
padding: 15px;
font-weight: 500;
font-size: 16px;
color: $dark-text-color;
.fa {
display: inline-block;
margin-right: 5px;
}
}
.account:last-child,
& > div:last-child .status {
border-bottom: 0;
}
& > .hashtag {
display: block;
padding: 10px;
color: $secondary-text-color;
text-decoration: none;
&:hover,
&:active,
&:focus {
color: lighten($secondary-text-color, 4%);
text-decoration: underline;
}
}
&:hover,
&:active,
&:focus {
color: lighten($secondary-text-color, 4%);
text-decoration: underline;
}
}
}

7
app/javascript/flavours/glitch/styles/components/media.scss

@ -320,6 +320,13 @@
background: rgba($gold-star, 0.3);
}
}
&.disabled {
color: $white;
background-color: transparent;
cursor: default;
opacity: 0.4;
}
}
}
}

11
app/javascript/flavours/glitch/styles/components/search.scss

@ -94,10 +94,15 @@
.search-results__header {
color: $dark-text-color;
background: lighten($ui-base-color, 2%);
border-bottom: 1px solid darken($ui-base-color, 4%);
padding: 15px 10px;
font-size: 14px;
padding: 15px;
font-weight: 500;
font-size: 16px;
cursor: default;
.fa {
display: inline-block;
margin-right: 5px;
}
}
.search-results__info {

1
app/javascript/flavours/glitch/styles/components/status.scss

@ -1095,6 +1095,7 @@ a.status-card.compact:hover {
&__account {
display: flex;
text-decoration: none;
overflow: hidden;
}
.account__avatar {

18
app/javascript/flavours/glitch/styles/forms.scss

@ -11,6 +11,24 @@ code {
margin: 0 auto;
}
.indicator-icon {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
color: $primary-text-color;
&.success {
background: $success-green;
}
&.failure {
background: $error-red;
}
}
.simple_form {
&.hidden {
display: none;

1
app/javascript/flavours/glitch/util/initial_state.js

@ -24,6 +24,7 @@ export const searchEnabled = getMeta('search_enabled');
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
export const pollLimits = (initialState && initialState.poll_limits);
export const invitesEnabled = getMeta('invites_enabled');
export const limitedFederationMode = getMeta('limited_federation_mode');
export const version = getMeta('version');
export const mascot = getMeta('mascot');
export const profile_directory = getMeta('profile_directory');

21
app/javascript/mastodon/actions/picture_in_picture.js

@ -22,13 +22,20 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
* @param {MediaProps} props
* @return {object}
*/
export const deployPictureInPicture = (statusId, accountId, playerType, props) => ({
type: PICTURE_IN_PICTURE_DEPLOY,
statusId,
accountId,
playerType,
props,
});
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
return (dispatch, getState) => {
// Do not open a player for a toot that does not exist
if (getState().hasIn(['statuses', statusId])) {
dispatch({
type: PICTURE_IN_PICTURE_DEPLOY,
statusId,
accountId,
playerType,
props,
});
}
};
};
/*
* @return {object}

7
app/javascript/mastodon/features/ui/components/link_footer.js

@ -3,7 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state';
import { invitesEnabled, limitedFederationMode, version, repository, source_url } from 'mastodon/initial_state';
import { logOut } from 'mastodon/utils/log_out';
import { openModal } from 'mastodon/actions/modal';
@ -47,11 +47,12 @@ class LinkFooter extends React.PureComponent {
return (
<div className='getting-started__footer'>
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite hocams' /></a> · </li>}
{withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
<li><a href='/apps/index.html' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
{!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>}
>>>>>>> remotes/upstream/main
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>

1
app/javascript/mastodon/initial_state.js

@ -14,6 +14,7 @@ export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
export const invitesEnabled = getMeta('invites_enabled');
export const limitedFederationMode = getMeta('limited_federation_mode');
export const repository = getMeta('repository');
export const source_url = getMeta('source_url');
export const version = getMeta('version');

3
app/javascript/mastodon/reducers/picture_in_picture.js

@ -1,4 +1,5 @@
import { PICTURE_IN_PICTURE_DEPLOY, PICTURE_IN_PICTURE_REMOVE } from 'mastodon/actions/picture_in_picture';
import { TIMELINE_DELETE } from '../actions/timelines';
const initialState = {
statusId: null,
@ -16,6 +17,8 @@ export default function pictureInPicture(state = initialState, action) {
return { statusId: action.statusId, accountId: action.accountId, type: action.playerType, ...action.props };
case PICTURE_IN_PICTURE_REMOVE:
return { ...initialState };
case TIMELINE_DELETE:
return (state.statusId === action.id) ? { ...initialState } : state;
default:
return state;
}

4
app/javascript/skins/glitch/metulayf/variables.scss

@ -10,9 +10,9 @@ $red-bookmark: $warning-red;
// Kafuka Theme Unique Colors. Color names may come later.
$classic-base-color: #0c0c0c;
$classic-primary-color: #a81717; //ee7487;
$classic-primary-color: #e6464b; //a81717; //ee7487;
$classic-secondary-color: #edfcf5;
$classic-highlight-color: #df1717; //e31837;
$classic-highlight-color: #a81717; //df1717; //e31837;
// Variables for defaults in UI
$base-shadow-color: $black !default;

12
app/javascript/styles/mastodon/boost.scss
File diff suppressed because it is too large
View File

8
app/javascript/styles/mastodon/components.scss

@ -4732,6 +4732,13 @@ a.status-card.compact:hover {
background: rgba($gold-star, 0.3);
}
}
&.disabled {
color: $white;
background-color: transparent;
cursor: default;
opacity: 0.4;
}
}
}
}
@ -7284,6 +7291,7 @@ noscript {
&__account {
display: flex;
text-decoration: none;
overflow: hidden;
}
.account__avatar {

18
app/javascript/styles/mastodon/forms.scss

@ -11,6 +11,24 @@ code {
margin: 0 auto;
}
.indicator-icon {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
color: $primary-text-color;
&.success {
background: $success-green;
}
&.failure {
background: $error-red;
}
}
.simple_form {
&.hidden {
display: none;

6
app/models/account.rb

@ -572,7 +572,11 @@ class Account < ApplicationRecord
def create_canonical_email_block!
return unless local? && user_email.present?
CanonicalEmailBlock.create(reference_account: self, email: user_email)
begin
CanonicalEmailBlock.create(reference_account: self, email: user_email)
rescue ActiveRecord::RecordNotUnique
# A canonical e-mail block may already exist for the same e-mail
end
end
def destroy_canonical_email_block!

1
app/models/account_stat.rb

@ -15,6 +15,7 @@
class AccountStat < ApplicationRecord
self.locking_column = nil
self.ignored_columns = %w(lock_version)
belongs_to :account, inverse_of: :account_stat

2
app/models/concerns/ldap_authenticable.rb

@ -15,10 +15,10 @@ module LdapAuthenticable
def ldap_get_user(attributes = {})
safe_username = attributes[Devise.ldap_uid.to_sym].first
if Devise.ldap_uid_conversion_enabled
keys = Regexp.union(Devise.ldap_uid_conversion_search.chars)
replacement = Devise.ldap_uid_conversion_replace
safe_username = safe_username.gsub(keys, replacement)
end

35
app/models/login_activity.rb

@ -0,0 +1,35 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: login_activities
#
# id :bigint(8) not null, primary key
# user_id :bigint(8) not null
# authentication_method :string
# provider :string
# success :boolean
# failure_reason :string
# ip :inet
# user_agent :string
# created_at :datetime
#
class LoginActivity < ApplicationRecord
enum authentication_method: { password: 'password', otp: 'otp', webauthn: 'webauthn', sign_in_token: 'sign_in_token', omniauth: 'omniauth' }
belongs_to :user
validates :authentication_method, inclusion: { in: authentication_methods.keys }
def detection
@detection ||= Browser.new(user_agent)
end
def browser
detection.id
end
def platform
detection.platform.id
end
end

14
app/models/report_filter.rb

@ -6,6 +6,7 @@ class ReportFilter
account_id
target_account_id
by_target_domain
target_origin
).freeze
attr_reader :params
@ -34,8 +35,21 @@ class ReportFilter
Report.where(account_id: value)
when :target_account_id
Report.where(target_account_id: value)
when :target_origin
target_origin_scope(value)
else
raise "Unknown filter: #{key}"
end
end
def target_origin_scope(value)
case value.to_sym
when :local
Report.where(target_account: Account.local)
when :remote
Report.where(target_account: Account.remote)
else
raise "Unknown value: #{value}"
end
end
end

25
app/models/user.rb

@ -42,6 +42,7 @@
# sign_in_token_sent_at :datetime
# webauthn_id :string
# sign_up_ip :inet
# skip_sign_in_token :boolean
#
class User < ApplicationRecord
@ -200,7 +201,7 @@ class User < ApplicationRecord
end
def suspicious_sign_in?(ip)
!otp_required_for_login? && current_sign_in_at.present? && current_sign_in_at < 2.weeks.ago && !recent_ip?(ip)
!otp_required_for_login? && !skip_sign_in_token? && current_sign_in_at.present? && !recent_ip?(ip)
end
def functional?