Merge pull request 'Merge in changes from upstream Mastodon/glitch-soc as of 20230511' (#59) from chore/merge-20230511-1 into main
ci/woodpecker/push/woodpecker Pipeline was successful Details

Reviewed-on: #59
pull/61/head
Ariadne Conill 2023-05-12 01:14:00 +00:00
commit 849d7bff51
1135 changed files with 28173 additions and 25444 deletions

View File

@ -26,7 +26,6 @@ services:
ports:
- '127.0.0.1:3000:3000'
- '127.0.0.1:4000:4000'
- '127.0.0.1:80:3000'
networks:
- external_network
- internal_network

View File

@ -7,6 +7,7 @@ module.exports = {
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:promise/recommended',
'plugin:jsdoc/recommended',
],
env: {
@ -27,6 +28,7 @@ module.exports = {
'import',
'promise',
'@typescript-eslint',
'formatjs',
],
parserOptions: {
@ -71,7 +73,7 @@ module.exports = {
'comma-style': ['warn', 'last'],
'consistent-return': 'error',
'dot-notation': 'error',
eqeqeq: 'error',
eqeqeq: ['error', 'always', { 'null': 'ignore' }],
indent: ['warn', 2],
'jsx-quotes': ['error', 'prefer-single'],
'no-case-declarations': 'off',
@ -218,6 +220,33 @@ module.exports = {
'promise/no-callback-in-promise': 'off',
'promise/no-nesting': 'off',
'promise/no-promise-in-callback': 'off',
'formatjs/blocklist-elements': 'error',
'formatjs/enforce-default-message': ['error', 'literal'],
'formatjs/enforce-description': 'off', // description values not currently used
'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
'formatjs/enforce-plural-rules': 'error',
'formatjs/no-camel-case': 'off', // disabledAccount is only non-conforming
'formatjs/no-complex-selectors': 'error',
'formatjs/no-emoji': 'error',
'formatjs/no-id': 'off', // IDs are used for translation keys
'formatjs/no-invalid-icu': 'error',
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
'formatjs/no-multiple-plurals': 'off', // Only used by hashtag.jsx
'formatjs/no-multiple-whitespaces': 'error',
'formatjs/no-offset': 'error',
'formatjs/no-useless-message': 'error',
'formatjs/prefer-formatted-message': 'error',
'formatjs/prefer-pound-in-plural': 'error',
'jsdoc/check-types': 'off',
'jsdoc/no-undefined-types': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-property-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-returns': 'off',
},
overrides: [
@ -250,10 +279,13 @@ module.exports = {
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:promise/recommended',
'plugin:jsdoc/recommended',
],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'jsdoc/require-jsdoc': 'off',
},
},
{

View File

@ -43,9 +43,16 @@ jobs:
type=edge,branch=main
type=sha,prefix=,format=long
- name: Generate version suffix
id: version_vars
if: github.repository == 'mastodon/mastodon' && github.event_name == 'push' && github.ref_name == 'main'
run: |
echo mastodon_version_suffix=+edge-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
- uses: docker/build-push-action@v4
with:
context: .
build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }}
platforms: linux/amd64,linux/arm64
provenance: false
builder: ${{ steps.buildx.outputs.name }}

60
.github/workflows/build-nightly.yml vendored Normal file
View File

@ -0,0 +1,60 @@
name: Build nightly container image
on:
workflow_dispatch:
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
permissions:
contents: read
packages: write
jobs:
build-nightly-image:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: hadolint/hadolint-action@v3.1.0
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- name: Log in to the Github Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v4
id: meta
with:
images: |
ghcr.io/mastodon/mastodon
flavor: |
latest=auto
tags: |
type=raw,value=nightly
type=schedule,pattern=nightly-{{date 'YYYY-MM-DD' tz='Etc/UTC'}}
labels: |
org.opencontainers.image.description=Nightly build image used for testing purposes
- name: Generate version suffix
id: version_vars
run: |
echo mastodon_version_suffix=+nightly-$(date +'%Y%m%d') >> $GITHUB_OUTPUT
- uses: docker/build-push-action@v4
with:
context: .
build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }}
platforms: linux/amd64,linux/arm64
provenance: false
builder: ${{ steps.buildx.outputs.name }}
push: ${{ github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -48,7 +48,7 @@ jobs:
run: yarn --frozen-lockfile
- name: ESLint
run: yarn test:lint:js
run: yarn test:lint:js --max-warnings 0
- name: Typecheck
run: yarn test:typecheck

View File

@ -9,7 +9,6 @@ on:
env:
BUNDLE_CLEAN: true
BUNDLE_FROZEN: true
BUNDLE_WITHOUT: 'development production'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -19,8 +18,17 @@ jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
mode:
- production
- test
env:
RAILS_ENV: test
RAILS_ENV: ${{ matrix.mode }}
BUNDLE_WITH: ${{ matrix.mode }}
OTP_SECRET: precompile_placeholder
SECRET_KEY_BASE: precompile_placeholder
steps:
- uses: actions/checkout@v3
@ -50,6 +58,7 @@ jobs:
./bin/rails assets:precompile
- uses: actions/upload-artifact@v3
if: matrix.mode == 'test'
with:
path: |-
./public/assets
@ -97,14 +106,13 @@ jobs:
PAM_ENABLED: true
PAM_DEFAULT_SERVICE: pam_test
PAM_CONTROLLED_SERVICE: pam_test_controlled
BUNDLE_WITH: 'pam_authentication'
BUNDLE_WITH: 'pam_authentication test'
CI_JOBS: ${{ matrix.ci_job }}/4
strategy:
fail-fast: false
matrix:
ruby-version:
- '2.7'
- '3.0'
- '3.1'
- '.ruby-version'
@ -136,10 +144,6 @@ jobs:
ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true
- name: Update system gems
if: matrix.ruby-version == '2.7'
run: gem update --system
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'

View File

@ -1 +1 @@
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio:/app/.apt/usr/lib/x86_64-linux-gnu/openblas-pthread

View File

@ -13,7 +13,7 @@ require:
- rubocop-capybara
AllCops:
TargetRubyVersion: 2.7 # Set to minimum supported version of CI
TargetRubyVersion: 3.0 # Set to minimum supported version of CI
DisplayCopNames: true
DisplayStyleGuide: true
ExtraDetails: true
@ -65,6 +65,7 @@ Metrics/AbcSize:
Metrics/BlockLength:
CountAsOne: ['array', 'hash', 'heredoc', 'method_call']
Exclude:
- 'config/routes.rb'
- 'lib/mastodon/*_cli.rb'
- 'lib/tasks/*.rake'
- 'app/models/concerns/account_associations.rb'
@ -85,6 +86,7 @@ Metrics/BlockLength:
- 'config/initializers/simple_form.rb'
- 'config/navigation.rb'
- 'config/routes.rb'
- 'config/routes/*.rb'
- 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb'
- 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb'
- 'lib/paperclip/gif_transcoder.rb'
@ -130,6 +132,7 @@ Metrics/ClassLength:
- 'app/services/activitypub/process_account_service.rb'
- 'app/services/activitypub/process_status_update_service.rb'
- 'app/services/backup_service.rb'
- 'app/services/bulk_import_service.rb'
- 'app/services/delete_account_service.rb'
- 'app/services/fan_out_on_write_service.rb'
- 'app/services/fetch_link_card_service.rb'
@ -158,6 +161,11 @@ Metrics/MethodLength:
Metrics/ModuleLength:
CountAsOne: [array, heredoc]
# Reason: Prevailing style is argument file paths
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath
Rails/FilePath:
EnforcedStyle: arguments
# Reason: Prevailing style uses numeric status codes, matches RSpec/Rails/HttpStatus
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railshttpstatus
Rails/HttpStatus:

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
ffmpeg
libopenblas0-pthread
libpq-dev
libxdamage1
libxfixes3

View File

@ -42,7 +42,7 @@ RUN \
bundle config set --local without 'development test' && \
bundle config set silence_root_warning true && \
bundle install -j"$(nproc)" && \
yarn install --immutable && \
yarn install && \
yarn cache clean
# Precompile assets
@ -58,12 +58,16 @@ ENV RAILS_ENV="production" \
ENV OTP_SECRET=precompile_placeholder \
SECRET_KEY_BASE=precompile_placeholder \
RAKE_NO_YARN_INSTALL_HACK=1
RUN mv ./emoji_data/all.json ./node_modules/emoji-mart/data/all.json && \
RUN mv ./emoji_data/all.json ./node_modules/emoji-mart/data/all.json && yarn install && \
bundle exec rails assets:precompile
FROM node:${NODE_VERSION}
# Use those args to specify your own version flags & suffixes
ARG MASTODON_VERSION_FLAGS=""
ARG MASTODON_VERSION_SUFFIX=""
ARG UID="991"
ARG GID="991"
@ -108,7 +112,9 @@ ENV RAILS_ENV="production" \
NODE_ENV="production" \
RAILS_SERVE_STATIC_FILES="true" \
BIND="0.0.0.0" \
SOURCE_TAG="${SOURCE_TAG}"
SOURCE_TAG="${SOURCE_TAG}" \
MASTODON_VERSION_FLAGS="${MASTODON_VERSION_FLAGS}" \
MASTODON_VERSION_SUFFIX="${MASTODON_VERSION_SUFFIX}"
# Set the run user
USER mastodon

13
Gemfile
View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 2.7.0', '< 3.3.0'
ruby '>= 3.0.0'
gem 'pkg-config', '~> 1.5'
@ -9,10 +9,10 @@ gem 'puma', '~> 6.2'
gem 'rails', '~> 6.1.7'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.6'
gem 'rack', '~> 2.2.7'
gem 'haml-rails', '~>2.0'
gem 'pg', '~> 1.4'
gem 'pg', '~> 1.5'
gem 'makara', '~> 0.5'
gem 'pghero'
gem 'dotenv-rails', '~> 2.8'
@ -30,7 +30,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.3'
gem 'devise', '~> 4.9'
gem 'devise-two-factor', '~> 4.0'
gem 'devise-two-factor', '~> 4.1'
group :pam_authentication, optional: true do
gem 'devise_pam_authenticatable2', '~> 9.2'
@ -76,7 +76,7 @@ gem 'redcarpet', '~> 3.6'
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 2.1'
gem 'ruby-progressbar', '~> 1.11'
gem 'ruby-progressbar', '~> 1.13'
gem 'sanitize', '~> 6.0'
gem 'scenic', '~> 1.7'
gem 'sidekiq', '~> 6.5'
@ -121,7 +121,7 @@ group :test do
gem 'capybara', '~> 3.39'
gem 'climate_control'
gem 'faker', '~> 3.2'
gem 'json-schema', '~> 3.0'
gem 'json-schema', '~> 4.0'
gem 'rack-test', '~> 2.1'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec_junit_formatter', '~> 0.6'
@ -163,3 +163,4 @@ gem 'hcaptcha', '~> 7.1'
gem 'cocoon', '~> 1.2'
gem 'net-http', '~> 0.3.2'
gem 'rubyzip', '~> 2.3'

View File

@ -104,12 +104,12 @@ GEM
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_encrypted (3.1.0)
attr_encrypted (4.0.0)
encryptor (~> 3.0.0)
attr_required (1.0.1)
awrence (1.2.1)
aws-eventstream (1.2.0)
aws-partitions (1.743.0)
aws-partitions (1.752.0)
aws-sdk-core (3.171.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
@ -118,7 +118,7 @@ GEM
aws-sdk-kms (1.63.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.120.1)
aws-sdk-s3 (1.121.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
@ -142,7 +142,7 @@ GEM
blurhash (0.1.7)
bootsnap (1.16.0)
msgpack (~> 1.2)
brakeman (5.4.0)
brakeman (5.4.1)
browser (5.3.1)
brpoplpush-redis_script (0.1.3)
concurrent-ruby (~> 1.0, >= 1.0.5)
@ -156,7 +156,7 @@ GEM
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
capistrano-bundler (2.0.1)
capistrano-bundler (2.1.0)
capistrano (~> 3.1)
capistrano-rails (1.6.2)
capistrano (~> 3.1)
@ -179,7 +179,7 @@ GEM
activesupport
cbor (0.5.9.6)
charlock_holmes (0.7.7)
chewy (7.3.0)
chewy (7.3.2)
activesupport (>= 5.2)
elasticsearch (>= 7.12.0, < 7.14.0)
elasticsearch-dsl
@ -189,26 +189,26 @@ GEM
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.2.2)
connection_pool (2.3.0)
connection_pool (2.4.0)
cose (1.3.0)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0)
crack (0.4.5)
rexml
crass (1.0.6)
css_parser (1.12.0)
css_parser (1.14.0)
addressable
date (3.3.3)
debug_inspector (1.0.0)
debug_inspector (1.1.0)
devise (4.9.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (4.0.2)
devise-two-factor (4.1.0)
activesupport (< 7.1)
attr_encrypted (>= 1.3, < 4, != 2)
attr_encrypted (>= 1.3, < 5, != 2)
devise (~> 4.0)
railties (< 7.1)
rotp (~> 6.0)
@ -241,7 +241,7 @@ GEM
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
excon (0.95.0)
excon (0.99.0)
fabrication (2.30.0)
faker (3.2.0)
i18n (>= 1.8.11, < 2)
@ -315,7 +315,7 @@ GEM
hashie (5.0.0)
hcaptcha (7.1.0)
json
highline (2.0.3)
highline (2.1.0)
hiredis (0.6.3)
hkdf (0.3.0)
htmlentities (4.3.4)
@ -349,15 +349,15 @@ GEM
ipaddress (0.8.3)
jmespath (1.6.2)
json (2.6.3)
json-canonicalization (0.3.1)
json-canonicalization (0.3.2)
json-jwt (1.15.3)
activesupport (>= 4.2)
aes_key_wrap
bindata
httpclient
json-ld (3.2.4)
json-ld (3.2.5)
htmlentities (~> 4.3)
json-canonicalization (~> 0.3)
json-canonicalization (~> 0.3, >= 0.3.2)
link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.15)
rack (>= 2.2, < 4)
@ -365,7 +365,7 @@ GEM
json-ld-preloaded (3.2.2)
json-ld (~> 3.2)
rdf (~> 3.2)
json-schema (3.0.0)
json-schema (4.0.0)
addressable (>= 2.8)
jsonapi-renderer (0.2.2)
jwt (2.7.0)
@ -381,8 +381,8 @@ GEM
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
launchy (2.5.0)
addressable (~> 2.7)
launchy (2.5.2)
addressable (~> 2.8)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
letter_opener_web (2.0.0)
@ -417,11 +417,11 @@ GEM
method_source (1.0.0)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mime-types-data (3.2023.0218.1)
mini_mime (1.1.2)
mini_portile2 (2.8.1)
minitest (5.18.0)
msgpack (1.6.0)
msgpack (1.7.0)
multi_json (1.15.0)
multipart-post (2.3.0)
net-http (0.3.2)
@ -438,7 +438,7 @@ GEM
net-ssh (>= 2.6.5, < 8.0.0)
net-smtp (0.3.3)
net-protocol
net-ssh (7.0.1)
net-ssh (7.1.0)
nio4r (2.5.9)
nokogiri (1.14.3)
mini_portile2 (~> 2.8.0)
@ -481,18 +481,18 @@ GEM
openssl (> 2.0)
orm_adapter (0.5.0)
ox (2.14.16)
parallel (1.22.1)
parser (3.2.2.0)
parallel (1.23.0)
parser (3.2.2.1)
ast (~> 2.4.1)
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.4.6)
pghero (3.3.2)
pg (1.5.3)
pghero (3.3.3)
activerecord (>= 6)
pkg-config (1.5.1)
posix-spawn (0.3.15)
premailer (1.18.0)
premailer (1.21.0)
addressable
css_parser (>= 1.12.0)
htmlentities (>= 4.0.0)
@ -502,13 +502,13 @@ GEM
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0)
public_suffix (5.0.1)
puma (6.2.1)
puma (6.2.2)
nio4r (~> 2.0)
pundit (2.3.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.6.2)
rack (2.2.6.4)
rack (2.2.7)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack-cors (2.0.1)
@ -568,25 +568,25 @@ GEM
redis (>= 4)
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.7.0)
regexp_parser (2.8.0)
request_store (1.5.1)
rack (>= 1.4)
responders (3.1.0)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.5)
rotp (6.2.0)
rotp (6.2.2)
rpam2 (4.0.2)
rqrcode (2.1.2)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.12.1)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.2)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.3)
rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
@ -604,7 +604,7 @@ GEM
rspec_chunked (0.6)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.49.0)
rubocop (1.50.2)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
@ -616,12 +616,12 @@ GEM
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.28.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.17.1)
rubocop-capybara (2.18.0)
rubocop (~> 1.41)
rubocop-performance (1.17.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.18.0)
rubocop-rails (2.19.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
@ -633,6 +633,7 @@ GEM
nokogiri (>= 1.10.5)
rexml
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
rufus-scheduler (3.8.2)
fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0)
@ -793,7 +794,7 @@ DEPENDENCIES
concurrent-ruby
connection_pool
devise (~> 4.9)
devise-two-factor (~> 4.0)
devise-two-factor (~> 4.1)
devise_pam_authenticatable2 (~> 9.2)
discard (~> 1.2)
doorkeeper (~> 5.6)
@ -819,7 +820,7 @@ DEPENDENCIES
idn-ruby
json-ld
json-ld-preloaded (~> 3.2)
json-schema (~> 3.0)
json-schema (~> 4.0)
kaminari (~> 1.2)
kt-paperclip (~> 7.1)!
letter_opener (~> 1.8)
@ -842,7 +843,7 @@ DEPENDENCIES
omniauth_openid_connect (~> 0.6.1)
ox (~> 2.14)
parslet
pg (~> 1.4)
pg (~> 1.5)
pghero
pkg-config (~> 1.5)
posix-spawn
@ -851,7 +852,7 @@ DEPENDENCIES
public_suffix (~> 5.0)
puma (~> 6.2)
pundit (~> 2.3)
rack (~> 2.2.6)
rack (~> 2.2.7)
rack-attack (~> 6.6)
rack-cors (~> 2.0)
rack-test (~> 2.1)
@ -873,7 +874,8 @@ DEPENDENCIES
rubocop-performance
rubocop-rails
rubocop-rspec
ruby-progressbar (~> 1.11)
ruby-progressbar (~> 1.13)
rubyzip (~> 2.3)
sanitize (~> 6.0)
scenic (~> 1.7)
sidekiq (~> 6.5)

View File

@ -8,7 +8,7 @@ class AboutController < ApplicationController
before_action :set_instance_presenter
def show
expires_in 0, public: true unless user_signed_in?
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
end
private

View File

@ -7,8 +7,9 @@ class AccountsController < ApplicationController
include AccountControllerConcern
include SignatureAuthentication
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
skip_before_action :require_functional!, unless: :whitelist_mode?
@ -16,7 +17,7 @@ class AccountsController < ApplicationController
def show
respond_to do |format|
format.html do
expires_in 0, public: true unless user_signed_in?
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
@rss_url = rss_url
end

View File

@ -7,10 +7,6 @@ class ActivityPub::BaseController < Api::BaseController
private
def set_cache_headers
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
end
def skip_temporary_suspension_response?
false
end

View File

@ -4,11 +4,12 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_items
before_action :set_size
before_action :set_type
before_action :set_cache_headers
def show
expires_in 3.minutes, public: public_fetch_mode?

View File

@ -4,9 +4,10 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
include SignatureVerification
include AccountOwnedConcern
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!
before_action :set_items
before_action :set_cache_headers
def show
expires_in 0, public: false

View File

@ -6,9 +6,10 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
vary_by -> { 'Signature' if authorized_fetch_mode? || page_requested? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_statuses
before_action :set_cache_headers
def show
if page_requested?
@ -16,6 +17,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
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
@ -80,8 +82,4 @@ 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

View File

@ -7,9 +7,10 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
DESCENDANTS_LIMIT = 60
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_status
before_action :set_cache_headers
before_action :set_replies
def index

View File

@ -14,6 +14,10 @@ class Admin::AnnouncementsController < Admin::BaseController
@announcement = Announcement.new
end
def edit
authorize :announcement, :update?
end
def create
authorize :announcement, :create?
@ -28,10 +32,6 @@ class Admin::AnnouncementsController < Admin::BaseController
end
end
def edit
authorize :announcement, :update?
end
def update
authorize :announcement, :update?

View File

@ -9,6 +9,8 @@ module Admin
before_action :set_pack
before_action :set_body_classes
before_action :set_cache_headers
after_action :verify_authorized
private
@ -21,6 +23,10 @@ module Admin
use_pack 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end

View File

@ -33,7 +33,7 @@ module Admin
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.save
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe
@domain_block.errors.delete(:domain)
render :new
else

View File

@ -16,6 +16,10 @@ module Admin
@role = UserRole.new
end
def edit
authorize @role, :update?
end
def create
authorize :user_role, :create?
@ -30,10 +34,6 @@ module Admin
end
end
def edit
authorize @role, :update?
end
def update
authorize @role, :update?

View File

@ -11,6 +11,10 @@ module Admin
@rule = Rule.new
end
def edit
authorize @rule, :update?
end
def create
authorize :rule, :create?
@ -24,10 +28,6 @@ module Admin
end
end
def edit
authorize @rule, :update?
end
def update
authorize @rule, :update?

View File

@ -11,6 +11,10 @@ module Admin
@warning_preset = AccountWarningPreset.new
end
def edit
authorize @warning_preset, :update?
end
def create
authorize :account_warning_preset, :create?
@ -24,10 +28,6 @@ module Admin
end
end
def edit
authorize @warning_preset, :update?
end
def update
authorize @warning_preset, :update?

View File

@ -10,12 +10,20 @@ module Admin
@webhooks = Webhook.page(params[:page])
end
def show
authorize @webhook, :show?
end
def new
authorize :webhook, :create?
@webhook = Webhook.new
end
def edit
authorize @webhook, :update?
end
def create
authorize :webhook, :create?
@ -28,14 +36,6 @@ module Admin
end
end
def show
authorize @webhook, :show?
end
def edit
authorize @webhook, :update?
end
def update
authorize @webhook, :update?

View File

@ -6,13 +6,14 @@ class Api::BaseController < ApplicationController
include RateLimitHeaders
include AccessTokenTrackingConcern
include ApiCachingConcern
skip_before_action :store_current_location
skip_before_action :require_functional!, unless: :whitelist_mode?
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
before_action :require_not_suspended!
before_action :set_cache_headers
vary_by 'Authorization'
protect_from_forgery with: :null_session
@ -148,10 +149,6 @@ class Api::BaseController < ApplicationController
doorkeeper_authorize!(*scopes) if doorkeeper_token
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
end
def disallow_unauthenticated_api_access?
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
end

View File

@ -6,6 +6,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
after_action :insert_pagination_headers
def index
cache_if_unauthenticated!
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end

View File

@ -6,6 +6,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
after_action :insert_pagination_headers
def index
cache_if_unauthenticated!
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end

View File

@ -5,6 +5,7 @@ class Api::V1::Accounts::LookupController < Api::BaseController
before_action :set_account
def show
cache_if_unauthenticated!
render json: @account, serializer: REST::AccountSerializer
end

View File

@ -7,6 +7,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
def index
cache_if_unauthenticated!
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end

View File

@ -18,6 +18,7 @@ class Api::V1::AccountsController < Api::BaseController
override_rate_limit_headers :follow, family: :follows
def show
cache_if_unauthenticated!
render json: @account, serializer: REST::AccountSerializer
end

View File

@ -16,6 +16,16 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
PAGINATION_PARAMS = %i(limit).freeze
def index
authorize :domain_allow, :index?
render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer
end
def show
authorize @domain_allow, :show?
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
end
def create
authorize :domain_allow, :create?
@ -29,16 +39,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
end
def index
authorize :domain_allow, :index?
render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer
end
def show
authorize @domain_allow, :show?
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
end
def destroy
authorize @domain_allow, :destroy?
UnallowDomainService.new.call(@domain_allow)

View File

@ -16,6 +16,16 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
PAGINATION_PARAMS = %i(limit).freeze
def index
authorize :domain_block, :index?
render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
end
def show
authorize @domain_block, :show?
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
end
def create
authorize :domain_block, :create?
@ -28,16 +38,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
end
def index
authorize :domain_block, :index?
render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
end
def show
authorize @domain_block, :show?
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
end
def update
authorize @domain_block, :update?
@domain_block.update!(domain_block_params)

View File

@ -18,15 +18,6 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
limit
).freeze
def create
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.create!(resource_params)
log_action :create, @email_domain_block
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
end
def index
authorize :email_domain_block, :index?
render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer
@ -37,6 +28,15 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
end
def create
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.create!(resource_params)
log_action :create, @email_domain_block
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
end
def destroy
authorize @email_domain_block, :destroy?
@email_domain_block.destroy!

View File

@ -18,13 +18,6 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
limit
).freeze
def create
authorize :ip_block, :create?
@ip_block = IpBlock.create!(resource_params)
log_action :create, @ip_block
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
end
def index
authorize :ip_block, :index?
render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer
@ -35,6 +28,13 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
end
def create
authorize :ip_block, :create?
@ip_block = IpBlock.create!(resource_params)
log_action :create, @ip_block
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
end
def update
authorize @ip_block, :update?
@ip_block.update(resource_params)

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true
class Api::V1::CustomEmojisController < Api::BaseController
skip_before_action :set_cache_headers
vary_by '', unless: :disallow_unauthenticated_api_access?
def index
expires_in 3.minutes, public: true
cache_even_if_authenticated! unless disallow_unauthenticated_api_access?
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
end
end

View File

@ -5,6 +5,7 @@ class Api::V1::DirectoriesController < Api::BaseController
before_action :set_accounts
def show
cache_if_unauthenticated!
render json: @accounts, each_serializer: REST::AccountSerializer
end

View File

@ -11,6 +11,10 @@ class Api::V1::FiltersController < Api::BaseController
render json: @filters, each_serializer: REST::V1::FilterSerializer
end
def show
render json: @filter, serializer: REST::V1::FilterSerializer
end
def create
ApplicationRecord.transaction do
filter_category = current_account.custom_filters.create!(filter_params)
@ -20,10 +24,6 @@ class Api::V1::FiltersController < Api::BaseController
render json: @filter, serializer: REST::V1::FilterSerializer
end
def show
render json: @filter, serializer: REST::V1::FilterSerializer
end
def update
ApplicationRecord.transaction do
@filter.update!(keyword_params)

View File

@ -3,11 +3,12 @@
class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
vary_by ''
def show
expires_in 1.day, public: true
cache_even_if_authenticated!
render_with_cache json: :activity, expires_in: 1.day
end

View File

@ -6,8 +6,15 @@ class Api::V1::Instances::DomainBlocksController < Api::BaseController
before_action :require_enabled_api!
before_action :set_domain_blocks
vary_by '', if: -> { Setting.show_domain_blocks == 'all' }
def index
expires_in 3.minutes, public: true
if Setting.show_domain_blocks == 'all'
cache_even_if_authenticated!
else
cache_if_unauthenticated!
end
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
end

View File

@ -2,11 +2,19 @@
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
skip_around_action :set_locale
before_action :set_extended_description
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if whitelist_mode?
end
def show
expires_in 3.minutes, public: true
cache_even_if_authenticated!
render json: @extended_description, serializer: REST::ExtendedDescriptionSerializer
end

View File

@ -3,11 +3,18 @@
class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
skip_around_action :set_locale
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if whitelist_mode?
end
def index
expires_in 1.day, public: true
cache_even_if_authenticated!
render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) }
end

View File

@ -5,8 +5,10 @@ class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
before_action :set_privacy_policy
vary_by ''
def show
expires_in 1.day, public: true
cache_even_if_authenticated!
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
end

View File

@ -2,10 +2,19 @@
class Api::V1::Instances::RulesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
skip_around_action :set_locale
before_action :set_rules
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if whitelist_mode?
end
def index
cache_even_if_authenticated!
render json: @rules, each_serializer: REST::RuleSerializer
end

View File

@ -5,8 +5,10 @@ class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
before_action :set_languages
vary_by ''
def show
expires_in 1.day, public: true
cache_even_if_authenticated!
render json: @languages
end

View File

@ -1,11 +1,18 @@
# frozen_string_literal: true
class Api::V1::InstancesController < Api::BaseController
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
skip_around_action :set_locale
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if whitelist_mode?
end
def show
expires_in 3.minutes, public: true
cache_even_if_authenticated!
render_with_cache json: InstancePresenter.new, serializer: REST::V1::InstanceSerializer, root: 'instance'
end
end

View File

@ -6,19 +6,20 @@ class Api::V1::MediaController < Api::BaseController
before_action :set_media_attachment, except: [:create]
before_action :check_processing, except: [:create]
def show
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
def create
@media_attachment = current_account.media_attachments.create!(media_attachment_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error
rescue Paperclip::Error => e
Rails.logger.error "#{e.class}: #{e.message}"
render json: processing_error, status: 500
end
def show
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
def update
@media_attachment.update!(updateable_media_attachment_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment

View File

@ -8,6 +8,7 @@ class Api::V1::PollsController < Api::BaseController
before_action :refresh_poll
def show
cache_if_unauthenticated!
render json: @poll, serializer: REST::PollSerializer, include_results: true
end

View File

@ -6,6 +6,10 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
before_action :set_push_subscription
before_action :check_push_subscription, only: [:show, :update]
def show
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def create
@push_subscription&.destroy!
@ -21,10 +25,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def show
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def update
@push_subscription.update!(data: data_params)
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer

View File

@ -8,6 +8,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
after_action :insert_pagination_headers
def index
cache_if_unauthenticated!
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end

View File

@ -7,6 +7,7 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
before_action :set_status
def show
cache_if_unauthenticated!
render json: @status.edits.includes(:account, status: [:account]), each_serializer: REST::StatusEditSerializer
end

View File

@ -8,6 +8,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
after_action :insert_pagination_headers
def index
cache_if_unauthenticated!
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end

View File

@ -24,11 +24,14 @@ class Api::V1::StatusesController < Api::BaseController
DESCENDANTS_DEPTH_LIMIT = 20
def show
cache_if_unauthenticated!
@status = cache_collection([@status], Status).first
render json: @status, serializer: REST::StatusSerializer
end
def context
cache_if_unauthenticated!
ancestors_limit = CONTEXT_LIMIT
descendants_limit = CONTEXT_LIMIT
descendants_depth_limit = nil

View File

@ -8,6 +8,7 @@ class Api::V1::TagsController < Api::BaseController
override_rate_limit_headers :follow, family: :follows
def show
cache_if_unauthenticated!
render json: @tag, serializer: REST::TagSerializer
end

View File

@ -5,6 +5,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
cache_if_unauthenticated!
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end

View File

@ -5,6 +5,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
cache_if_unauthenticated!
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::Trends::LinksController < Api::BaseController
vary_by 'Authorization, Accept-Language'
before_action :set_links
after_action :insert_pagination_headers
@ -8,6 +10,7 @@ class Api::V1::Trends::LinksController < Api::BaseController
DEFAULT_LINKS_LIMIT = 10
def index
cache_if_unauthenticated!
render json: @links, each_serializer: REST::Trends::LinkSerializer
end

View File

@ -1,11 +1,14 @@
# frozen_string_literal: true
class Api::V1::Trends::StatusesController < Api::BaseController
vary_by 'Authorization, Accept-Language'
before_action :set_statuses
after_action :insert_pagination_headers
def index
cache_if_unauthenticated!
render json: @statuses, each_serializer: REST::StatusSerializer
end

View File

@ -8,6 +8,7 @@ class Api::V1::Trends::TagsController < Api::BaseController
DEFAULT_TAGS_LIMIT = (ENV['MAX_TRENDING_TAGS'] || 10).to_i
def index
cache_if_unauthenticated!
render json: @tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@tags, current_user&.account_id)
end

View File

@ -12,13 +12,13 @@ class Api::V2::Filters::KeywordsController < Api::BaseController
render json: @keywords, each_serializer: REST::FilterKeywordSerializer
end
def create
@keyword = current_account.custom_filters.find(params[:filter_id]).keywords.create!(resource_params)
def show
render json: @keyword, serializer: REST::FilterKeywordSerializer
end
def show
def create
@keyword = current_account.custom_filters.find(params[:filter_id]).keywords.create!(resource_params)
render json: @keyword, serializer: REST::FilterKeywordSerializer
end

View File

@ -12,13 +12,13 @@ class Api::V2::Filters::StatusesController < Api::BaseController
render json: @status_filters, each_serializer: REST::FilterStatusSerializer
end
def create
@status_filter = current_account.custom_filters.find(params[:filter_id]).statuses.create!(resource_params)
def show
render json: @status_filter, serializer: REST::FilterStatusSerializer
end
def show
def create
@status_filter = current_account.custom_filters.find(params[:filter_id]).statuses.create!(resource_params)
render json: @status_filter, serializer: REST::FilterStatusSerializer
end

View File

@ -11,13 +11,13 @@ class Api::V2::FiltersController < Api::BaseController
render json: @filters, each_serializer: REST::FilterSerializer, rules_requested: true
end
def create
@filter = current_account.custom_filters.create!(resource_params)
def show
render json: @filter, serializer: REST::FilterSerializer, rules_requested: true
end
def show
def create
@filter = current_account.custom_filters.create!(resource_params)
render json: @filter, serializer: REST::FilterSerializer, rules_requested: true
end

View File

@ -2,7 +2,7 @@
class Api::V2::InstancesController < Api::V1::InstancesController
def show
expires_in 3.minutes, public: true
cache_even_if_authenticated!
render_with_cache json: InstancePresenter.new, serializer: REST::InstanceSerializer, root: 'instance'
end
end

View File

@ -6,7 +6,8 @@ class Api::V2::MediaController < Api::V1::MediaController
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: @media_attachment.not_processed? ? 202 : 200
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error
rescue Paperclip::Error => e
Rails.logger.error "#{e.class}: #{e.message}"
render json: processing_error, status: 500
end
end

View File

@ -21,6 +21,8 @@ class ApplicationController < ActionController::Base
helper_method :omniauth_only?
helper_method :sso_account_settings
helper_method :whitelist_mode?
helper_method :body_class_string
helper_method :skip_csrf_meta_tags?
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
rescue_from Mastodon::NotPermittedError, with: :forbidden
@ -37,9 +39,11 @@ class ApplicationController < ActionController::Base
service_unavailable
end
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :store_referrer, except: :raise_not_found, if: :devise_controller?
before_action :require_functional!, if: :user_signed_in?
before_action :set_cache_control_defaults
skip_before_action :verify_authenticity_token, only: :raise_not_found
def raise_not_found
@ -56,14 +60,25 @@ class ApplicationController < ActionController::Base
!authorized_fetch_mode?
end
def store_current_location
store_location_for(:user, request.url) unless [:json, :rss].include?(request.format&.to_sym)
def store_referrer
return if request.referer.blank?
redirect_uri = URI(request.referer)
return if redirect_uri.path.start_with?('/auth')
stored_url = redirect_uri.to_s if redirect_uri.host == request.host && redirect_uri.port == request.port
store_location_for(:user, stored_url)
end
def require_functional!
redirect_to edit_user_registration_path unless current_user.functional?
end
def skip_csrf_meta_tags?
false
end
def after_sign_out_path_for(_resource_or_scope)
if ENV['OMNIAUTH_ONLY'] == 'true' && ENV['OIDC_ENABLED'] == 'true'
'/auth/auth/openid_connect/logout'
@ -127,7 +142,7 @@ class ApplicationController < ActionController::Base
end
def sso_account_settings
ENV.fetch('SSO_ACCOUNT_SETTINGS')
ENV.fetch('SSO_ACCOUNT_SETTINGS', nil)
end
def current_account
@ -142,6 +157,10 @@ class ApplicationController < ActionController::Base
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
end
def body_class_string
@body_classes || ''
end
def respond_with_error(code)
respond_to do |format|
format.any do
@ -151,4 +170,8 @@ class ApplicationController < ActionController::Base
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
end
end
def set_cache_control_defaults
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -25,16 +25,16 @@ class Auth::RegistrationsController < Devise::RegistrationsController
super(&:build_invite_request)
end
def destroy
not_found
end
def update
super do |resource|
resource.clear_other_sessions(current_session.session_id) if resource.saved_change_to_encrypted_password?
end
end
def destroy
not_found
end
protected
def update_resource(resource, params)
@ -157,6 +157,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -60,7 +60,7 @@ class AuthorizeInteractionsController < ApplicationController
end
def uri_param
params[:uri] || params.fetch(:acct, '').gsub(/\Aacct:/, '')
params[:uri] || params.fetch(:acct, '').delete_prefix('acct:')
end
def set_body_classes

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module ApiCachingConcern
extend ActiveSupport::Concern
def cache_if_unauthenticated!
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
end
def cache_even_if_authenticated!
expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless whitelist_mode?
end
end

View File

@ -155,8 +155,30 @@ module CacheConcern
end
end
class_methods do
def vary_by(value, **kwargs)
before_action(**kwargs) do |controller|
response.headers['Vary'] = value.respond_to?(:call) ? controller.instance_exec(&value) : value
end
end
end
included do
after_action :enforce_cache_control!
end
# Prevents high-entropy headers such as `Cookie`, `Signature` or `Authorization`
# from being used as cache keys, while allowing to `Vary` on them (to not serve
# anonymous cached data to authenticated requests when authentication matters)
def enforce_cache_control!
vary = response.headers['Vary']&.split&.map { |x| x.strip.downcase }
return unless vary.present? && %w(cookie authorization signature).any? { |header| vary.include?(header) && request.headers[header].present? }
response.cache_control.replace(private: true, no_store: true)
end
def render_with_cache(**options)
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
expires_in = options.delete(:expires_in) || 3.minutes
@ -176,10 +198,6 @@ module CacheConcern
end
end
def set_cache_headers
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
end
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:with_includes)

View File

@ -180,14 +180,15 @@ module SignatureVerification
def build_signed_string
signed_headers.map do |signed_header|
if signed_header == Request::REQUEST_TARGET
case signed_header
when Request::REQUEST_TARGET
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
elsif signed_header == '(created)'
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?
"(created): #{signature_params['created']}"
elsif signed_header == '(expires)'
when '(expires)'
raise SignatureVerificationError, 'Invalid pseudo-header (expires) for rsa-sha256' unless signature_algorithm == 'hs2019'
raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
@ -244,7 +245,7 @@ module SignatureVerification
end
if key_id.start_with?('acct:')
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''), suppress_errors: false) }
stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) }
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) }

View File

@ -7,6 +7,12 @@ module WebAppControllerConcern
prepend_before_action :redirect_unauthenticated_to_permalinks!
before_action :set_pack
before_action :set_app_body_class
vary_by 'Accept, Accept-Language, Cookie'
end
def skip_csrf_meta_tags?
current_user.nil?
end
def set_app_body_class

View File

@ -1,18 +1,8 @@
# frozen_string_literal: true
class CustomCssController < ApplicationController
skip_before_action :store_current_location
skip_before_action :require_functional!
skip_before_action :update_user_sign_in
skip_before_action :set_session_activity
skip_around_action :set_locale
before_action :set_cache_headers
class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
def show
expires_in 3.minutes, public: true
request.session_options[:skip] = true
render content_type: 'text/css'
end
end

View File

@ -10,6 +10,7 @@ class Disputes::BaseController < ApplicationController
before_action :set_body_classes
before_action :authenticate_user!
before_action :set_pack
before_action :set_cache_headers
private
@ -20,4 +21,8 @@ class Disputes::BaseController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -2,15 +2,12 @@
class EmojisController < ApplicationController
before_action :set_emoji
before_action :set_cache_headers
vary_by -> { 'Signature' if authorized_fetch_mode? }
def show
respond_to do |format|
format.json do
expires_in 3.minutes, public: true
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
end
end
expires_in 3.minutes, public: true
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
end
private

View File

@ -8,6 +8,7 @@ class Filters::StatusesController < ApplicationController
before_action :set_status_filters
before_action :set_pack
before_action :set_body_classes
before_action :set_cache_headers
PER_PAGE = 20
@ -49,4 +50,8 @@ class Filters::StatusesController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -7,6 +7,7 @@ class FiltersController < ApplicationController
before_action :set_filter, only: [:edit, :update, :destroy]
before_action :set_pack
before_action :set_body_classes
before_action :set_cache_headers
def index
@filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
@ -17,6 +18,8 @@ class FiltersController < ApplicationController
@filter.keywords.build
end
def edit; end
def create
@filter = current_account.custom_filters.build(resource_params)
@ -27,8 +30,6 @@ class FiltersController < ApplicationController
end
end
def edit; end
def update
if @filter.update(resource_params)
redirect_to filters_path
@ -59,4 +60,8 @@ class FiltersController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -5,8 +5,9 @@ class FollowerAccountsController < ApplicationController
include SignatureVerification
include WebAppControllerConcern
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, unless: :whitelist_mode?
@ -14,7 +15,7 @@ class FollowerAccountsController < ApplicationController
def index
respond_to do |format|
format.html do
expires_in 0, public: true unless user_signed_in?
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
end
format.json do

View File

@ -5,8 +5,9 @@ class FollowingAccountsController < ApplicationController
include SignatureVerification
include WebAppControllerConcern
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, unless: :whitelist_mode?
@ -14,7 +15,7 @@ class FollowingAccountsController < ApplicationController
def index
respond_to do |format|
format.html do
expires_in 0, public: true unless user_signed_in?
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
end
format.json do

View File

@ -6,7 +6,7 @@ class HomeController < ApplicationController
before_action :set_instance_presenter
def index
expires_in 0, public: true unless user_signed_in?
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
end
private

View File

@ -1,10 +1,13 @@
# frozen_string_literal: true
class InstanceActorsController < ApplicationController
include AccountControllerConcern
class InstanceActorsController < ActivityPub::BaseController
vary_by ''
skip_before_action :check_account_confirmation
skip_around_action :set_locale
serialization_scope nil
before_action :set_account
skip_before_action :require_functional!
skip_before_action :update_user_sign_in
def show
expires_in 10.minutes, public: true

View File

@ -9,7 +9,7 @@ class IntentsController < ApplicationController
if uri.scheme == 'web+mastodon'
case uri.host
when 'follow'
return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].gsub(/\Aacct:/, ''))
return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].delete_prefix('acct:'))
when 'share'
return redirect_to share_path(text: uri.query_values['text'])
end

View File

@ -8,6 +8,7 @@ class InvitesController < ApplicationController
before_action :authenticate_user!
before_action :set_pack
before_action :set_body_classes
before_action :set_cache_headers
def index
authorize :invite, :create?
@ -54,4 +55,8 @@ class InvitesController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
class ManifestsController < ApplicationController
skip_before_action :store_current_location
skip_before_action :require_functional!
class ManifestsController < ActionController::Base # rubocop:disable Rails/ApplicationController
# Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user`
# and thus re-issuing session cookies
serialization_scope nil
def show
expires_in 3.minutes, public: true

View File

@ -3,7 +3,6 @@
class MediaController < ApplicationController
include Authorization
skip_before_action :store_current_location
skip_before_action :require_functional!, unless: :whitelist_mode?
before_action :authenticate_user!, if: :whitelist_mode?

View File

@ -6,7 +6,6 @@ class MediaProxyController < ApplicationController
include Redisable
include Lockable
skip_before_action :store_current_location
skip_before_action :require_functional!
before_action :authenticate_user!, if: :whitelist_mode?
@ -17,7 +16,7 @@ class MediaProxyController < ApplicationController
rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
def show
with_lock("media_download:#{params[:id]}") do
with_redis_lock("media_download:#{params[:id]}") do
@media_attachment = MediaAttachment.remote.attached.find(params[:id])
authorize @media_attachment.status, :show?
redownload! if @media_attachment.needs_redownload? && !reject_media?

View File

@ -39,6 +39,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -8,6 +8,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :set_pack
before_action :require_not_suspended!, only: :destroy
before_action :set_body_classes
before_action :set_cache_headers
skip_before_action :require_functional!
@ -35,4 +36,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
def require_not_suspended!
forbidden if current_account.suspended?
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -8,7 +8,7 @@ class PrivacyController < ApplicationController
before_action :set_instance_presenter
def show
expires_in 0, public: true if current_account.nil?
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
end
private

View File

@ -8,6 +8,7 @@ class RelationshipsController < ApplicationController
before_action :set_pack
before_action :set_relationships, only: :show
before_action :set_body_classes
before_action :set_cache_headers
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
@ -75,4 +76,8 @@ class RelationshipsController < ApplicationController
def set_pack
use_pack 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View File

@ -8,6 +8,8 @@ class Settings::ApplicationsController < Settings::BaseController
@applications = current_user.applications.order(id: :desc).page(params[:page])
end
def show; end
def new
@application = Doorkeeper::Application.new(
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
@ -15,8 +17,6 @@ class Settings::ApplicationsController < Settings::BaseController
)
end
def show; end
def create
@application = current_user.applications.build(application_params)

View File

@ -19,7 +19,7 @@ class Settings::BaseController < ApplicationController
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
response.cache_control.replace(private: true, no_store: true)
end
def require_not_suspended!

View File

@ -15,7 +15,7 @@ class Settings::ExportsController < Settings::BaseController
def create
backup = nil
with_lock("backup:#{current_user.id}") do
with_redis_lock("backup:#{current_user.id}") do
authorize :backup, :create?
backup = current_user.backups.create!
end

View File

@ -1,31 +1,97 @@
# frozen_string_literal: true
class Settings::ImportsController < Settings::BaseController
before_action :set_account
require 'csv'
def show
@import = Import.new
class Settings::ImportsController < Settings::BaseController
before_action :set_bulk_import, only: [:show, :confirm, :destroy]
before_action :set_recent_imports, only: [:index]
TYPE_TO_FILENAME_MAP = {
following: 'following_accounts_failures.csv',
blocking: 'blocked_accounts_failures.csv',
muting: 'muted_accounts_failures.csv',
domain_blocking: 'blocked_domains_failures.csv',
bookmarks: 'bookmarks_failures.csv',
}.freeze
TYPE_TO_HEADERS_MAP = {
following: ['Account address', 'Show boosts', 'Notify on new posts', 'Languages'],
blocking: false,
muting: ['Account address', 'Hide notifications'],
domain_blocking: false,
bookmarks: false,
}.freeze
def index
@import = Form::Import.new(current_account: current_account)
end
def show; end
def failures
@bulk_import = current_account.bulk_imports.where(state: :finished).find(params[:id])
respond_to do |format|
format.csv do
filename = TYPE_TO_FILENAME_MAP[@bulk_import.type.to_sym]
headers = TYPE_TO_HEADERS_MAP[@bulk_import.type.to_sym]
export_data = CSV.generate(headers: headers, write_headers: true) do |csv|
@bulk_import.rows.find_each do |row|
case @bulk_import.type.to_sym
when :following
csv << [row.data['acct'], row.data.fetch('show_reblogs', true), row.data.fetch('notify', false), row.data['languages']&.join(', ')]
when :blocking
csv << [row.data['acct']]
when :muting
csv << [row.data['acct'], row.data.fetch('hide_notifications', true)]
when :domain_blocking
csv << [row.data['domain']]
when :bookmarks
csv << [row.data['uri']]
end
end
end
send_data export_data, filename: filename
end
end
end
def confirm
@bulk_import.update!(state: :scheduled)
BulkImportWorker.perform_async(@bulk_import.id)
redirect_to settings_imports_path, notice: I18n.t('imports.success')
end
def create
@import = Import.new(import_params)
@import.account = @account
@import = Form::Import.new(import_params.merge(current_account: current_account))
if @import.save
ImportWorker.perform_async(@import.id)
redirect_to settings_import_path, notice: I18n.t('imports.success')
redirect_to settings_import_path(@import.bulk_import.id)
else
render :show
# We need to set recent imports as we are displaying the index again
set_recent_imports
render :index
end
end
def destroy
@bulk_import.destroy!
redirect_to settings_imports_path
end
private
def set_account
@account = current_user.account
def import_params
params.require(:form_import).permit(:data, :type, :mode)
end
def import_params
params.require(:import).permit(:data, :type, :mode)
def set_bulk_import
@bulk_import = current_account.bulk_imports.where(state: :unconfirmed).find(params[:id])
end
def set_recent_imports
@recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(10)
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
class Settings::Preferences::AppearanceController < Settings::PreferencesController
class Settings::Preferences::AppearanceController < Settings::Preferences::BaseController
private
def after_update_redirect_path

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
class Settings::PreferencesController < Settings::BaseController
class Settings::Preferences::BaseController < Settings::BaseController
def show; end
def update
@ -15,7 +15,7 @@ class Settings::PreferencesController < Settings::BaseController
private
def after_update_redirect_path
settings_preferences_path
raise 'Override in controller'
end
def user_params

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
class Settings::Preferences::NotificationsController < Settings::PreferencesController
class Settings::Preferences::NotificationsController < Settings::Preferences::BaseController
private
def after_update_redirect_path

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
class Settings::Preferences::OtherController < Settings::PreferencesController
class Settings::Preferences::OtherController < Settings::Preferences::BaseController
private
def after_update_redirect_path

View File

@ -8,9 +8,8 @@ module Settings
before_action :require_otp_enabled
before_action :require_webauthn_enabled, only: [:index, :destroy]
def new; end
def index; end
def new; end
def options
current_user.update(webauthn_id: WebAuthn.generate_user_id) unless current_user.webauthn_id

Some files were not shown because too many files have changed in this diff Show More