Compare commits

..

20 Commits

Author SHA1 Message Date
Eugen Rochko
a583e54023 Bump version to 3.2.1 2020-10-19 16:07:06 +02:00
ThibG
4ea7193f0a Add support for latest HTTP Signatures spec draft (#14556)
* Add support for latest HTTP Signatures spec draft

https://www.ietf.org/id/draft-ietf-httpbis-message-signatures-00.html

- add support for the “hs2019” signature algorithm (assumed to be equivalent
  to RSA-SHA256, since we do not have a mechanism to specify the algorithm
  within the key metadata yet)
- add support for (created) and (expires) pseudo-headers and related
  signature parameters, when using the hs2019 signature algorithm
- adjust default “headers” parameter while being backwards-compatible with
  previous implementation
- change the acceptable time window logic from 12 hours surrounding the “date”
  header to accepting signatures created up to 1 hour in the future and
  expiring up to 1 hour in the past (but only allowing expiration dates up to
  12 hours after the creation date)
  This doesn't conform with the current draft, as it doesn't permit accounting
  for clock skew.
  This, however, should be addressed in a next version of the draft:
  https://github.com/httpwg/http-extensions/pull/1235

* Add additional signature requirements

* Rewrite signature params parsing using Parslet

* Make apparent which signature algorithm Mastodon on verification failure

Mastodon uses RSASSA-PKCS1-v1_5, which is not recommended for new applications,
and new implementers may thus unknowingly use RSASSA-PSS.

* Add workaround for PeerTube's invalid signature header

The previous parser allowed incorrect Signature headers, such as
those produced by old versions of the `http-signature` node.js package,
and seemingly used by PeerTube.

This commit adds a workaround for that.

* Fix `signature_key_id` raising an exception

Previously, parsing failures would result in `signature_key_id` being nil,
but the parser changes made that result in an exception.

This commit changes the `signature_key_id` method to return `nil` in case
of parsing failures.

* Move extra HTTP signature helper methods to private methods

* Relax (request-target) requirement to (request-target) || digest

This lets requests from Plume work without lowering security significantly.
2020-10-19 15:41:25 +02:00
ThibG
aa98655cf6 Fix dereferencing remote statuses not using the correct account (#14656)
Follow-up to #14359

In the case of limited toots, the receiver may not be explicitly part of the
audience. If a specific user's inbox URI was specified, it makes sense to
dereference the toot from the corresponding user, instead of trying to find
someone in the explicit audience.
2020-10-19 15:41:11 +02:00
Tdxdxoz
dd3a86eb04 Fix: also use custom private boost icon for detailed status (#14471)
* use custom private boost icon for detail status

* only use className
2020-10-19 15:40:51 +02:00
ThibG
aea0161e83 Add support for inlined objects in activity audience (#14514)
* Add support for inlined objects in activity audience

* Add tests
2020-10-19 15:40:42 +02:00
ThibG
8b448aecef Fix tootctl media commands not handling snowflake ids for media_attachments (#14536) 2020-10-19 15:40:34 +02:00
ThibG
6db143e424 Fix crash when failing to load emoji picker (#14525)
Fixes #14523
2020-10-19 15:40:22 +02:00
ThibG
3b699f1732 Fix thumbnail color extraction (#14464)
* Fix contrast calculation for thumbnail color extraction

Luminance calculation was using 0-255 RGB values instead of 0-1 sRGB values,
leading to incorrectly-computed contrast values.

Since we use ColorDiff already, just use its XYZ colorspace conversion code
to get the value.

* Require at least 3:1 contrast for both accent and foreground colors

* Lower required contrast for the accent color
2020-10-19 15:40:14 +02:00
ThibG
1995a5cb34 Fix audio/video player not using CDN_HOST in media paths on public pages (#14486) 2020-10-19 15:40:03 +02:00
ThibG
469c4c78a3 Fix audio player on Safari (#14485) 2020-10-19 15:39:48 +02:00
ThibG
399c5f0900 Change content-type to be always computed from file data (#14452)
* Change content-type to be always computed from file data

Restore previous behavior, detecting the content-type isn't very
expensive, and some instances may serve files as application/octet-stream
regardless of their true type, making fetching media from them fail, while
it used to work pre-3.2.0.

* Add test
2020-10-19 15:39:28 +02:00
ThibG
856cb96a2b Fix new audio player features not working on Safari (#14465)
Fixes #14462
2020-10-19 15:39:18 +02:00
Takeshi Umeda
58c59af573 Fix an error when file_file_size is nil in tootctl media remove (#14657) 2020-10-19 14:49:31 +02:00
Eugen Rochko
3f4cceebd6 Fix videos with near-60 fps being rejected (#14684)
Fix #14668
2020-10-19 14:49:14 +02:00
Eugen Rochko
ce6aaed432 Remove dependency on goldfinger gem (#14919)
There are edge cases where requests to certain hosts timeout when
using the vanilla HTTP.rb gem, which the goldfinger gem uses. Now
that we no longer need to support OStatus servers, webfinger logic
is so simple that there is no point encapsulating it in a gem, so
we can just use our own Request class. With that, we benefit from
more robust timeout code and IPv4/IPv6 resolution.

Fix #14091
2020-10-19 14:48:54 +02:00
Eugen Rochko
8f79ed0487 Fix reported statuses not being included in warning e-mail (#14778) 2020-10-19 14:46:35 +02:00
ThibG
4acfc3ce83 Fix handling of Reject Follow when a matching follow relationship exists (#14479)
* Add tests

* Fix handling of Reject Follow when a matching follow relationship exists

Regression from #12199
2020-10-19 14:46:10 +02:00
Takeshi Umeda
c98b7751ca Fix limited follower id in fan-out-on-write service (#14709) 2020-10-19 14:45:44 +02:00
Eugen Rochko
0abfa06b2f Fix inefficiencies in fan-out-on-write service (#14682) 2020-10-19 14:45:30 +02:00
ThibG
aecdaf5a8c Do not serve account actors at all in limited federation mode (#14800)
* Do not serve account actors at all in limited federation mode

When an account is fetched without a signature from an allowed instance,
return an error.

This isn't really an improvement in security, as the only information that was
previously returned was required protocol-level info, and the only personal bit
was the existence of the account. The existence of the account can still be
checked by issuing a webfinger query, as those are accepted without signatures.

However, this change makes it so that unallowed instances won't create account
records on their end when they find a reference to an unknown account.

The previous behavior of rendering a limited list of fields, instead of not
rendering the actor at all, was in order to prevent situations in which two
instances in Authorized Fetch mode or Limited Federation mode would fail to
reach each other because resolving an account would require a signed query…
from an account which can only be fetched with a signed query itself. However,
this should now be fine as fetching accounts is done by signing on behalf of
the special instance actor, which does not require any kind of valid signature
to be fetched.

* Fix tests
2020-10-19 14:45:12 +02:00
876 changed files with 11021 additions and 35362 deletions

View File

@@ -27,10 +27,10 @@ plugins:
enabled: true
eslint:
enabled: true
channel: eslint-7
channel: eslint-6
rubocop:
enabled: true
channel: rubocop-0-92
channel: rubocop-0-82
sass-lint:
enabled: true
exclude_patterns:

View File

@@ -1,7 +1,7 @@
---
name: Bug Report
about: If something isn't working as expected
labels: bug
---
<!-- Make sure that you are submitting a new bug that was not previously reported or already fixed -->

View File

@@ -1,6 +1,7 @@
---
name: Feature Request
about: I have a suggestion
---
<!-- Please use a concise and distinct title for the issue -->

View File

@@ -11,7 +11,7 @@ updates:
interval: weekly
open-pull-requests-limit: 99
allow:
- dependency-type: direct
- dependency-type: all
- package-ecosystem: bundler
directory: "/"
@@ -19,4 +19,4 @@ updates:
interval: weekly
open-pull-requests-limit: 99
allow:
- dependency-type: direct
- dependency-type: all

View File

@@ -25,68 +25,30 @@ Layout/AccessModifierIndentation:
Layout/EmptyLineAfterMagicComment:
Enabled: false
Layout/EmptyLineAfterGuardClause:
Enabled: false
Layout/EmptyLinesAroundAttributeAccessor:
Enabled: true
Layout/HashAlignment:
Enabled: false
# EnforcedHashRocketStyle: table
# EnforcedColonStyle: table
Layout/SpaceAroundMethodCallOperator:
Enabled: true
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: space
Lint/DeprecatedOpenSSLConstant:
Enabled: true
Lint/DuplicateElsifCondition:
Enabled: true
Lint/MixedRegexpCaptureTypes:
Enabled: true
Lint/RaiseException:
Enabled: true
Lint/StructNewOverride:
Enabled: true
Lint/UselessAccessModifier:
ContextCreatingMethods:
- class_methods
Metrics/AbcSize:
Max: 100
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/BlockLength:
Max: 55
Max: 35
Exclude:
- 'lib/tasks/**/*'
- 'lib/mastodon/*_cli.rb'
Metrics/BlockNesting:
Max: 3
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/ClassLength:
CountComments: false
Max: 400
Exclude:
- 'lib/mastodon/*_cli.rb'
Max: 300
Metrics/CyclomaticComplexity:
Max: 25
Exclude:
- 'lib/mastodon/*_cli.rb'
Layout/LineLength:
AllowURI: true
@@ -94,9 +56,7 @@ Layout/LineLength:
Metrics/MethodLength:
CountComments: false
Max: 65
Exclude:
- 'lib/mastodon/*_cli.rb'
Max: 55
Metrics/ModuleLength:
CountComments: false
@@ -107,90 +67,34 @@ Metrics/ParameterLists:
CountKeywordArgs: true
Metrics/PerceivedComplexity:
Max: 25
Max: 20
Naming/MemoizedInstanceVariableName:
Enabled: false
Naming/MethodParameterName:
Enabled: true
Rails:
Enabled: true
Rails/ApplicationController:
Enabled: false
Exclude:
- 'app/controllers/well_known/**/*.rb'
Rails/BelongsTo:
Enabled: false
Rails/ContentTag:
Enabled: false
Rails/EnumHash:
Enabled: false
Rails/HasAndBelongsToMany:
Enabled: false
Rails/SkipsModelValidations:
Enabled: false
Rails/HttpStatus:
Enabled: false
Rails/Exit:
Exclude:
- 'lib/mastodon/*'
- 'lib/cli.rb'
Rails/FilePath:
Enabled: false
Rails/HasAndBelongsToMany:
Enabled: false
Rails/HasManyOrHasOneDependent:
Enabled: false
Rails/HelperInstanceVariable:
Enabled: false
Rails/HttpStatus:
Enabled: false
Rails/IndexBy:
Enabled: false
Rails/InverseOf:
Enabled: false
Rails/LexicallyScopedActionFilter:
Enabled: false
Rails/OutputSafety:
Enabled: true
Rails/RakeEnvironment:
Enabled: false
Rails/RedundantForeignKey:
Enabled: false
Rails/SkipsModelValidations:
Enabled: false
Rails/UniqueValidationWithoutIndex:
Enabled: false
Style/AccessorGrouping:
Enabled: true
Style/AccessModifierDeclarations:
Enabled: false
Style/ArrayCoercion:
Enabled: true
Style/BisectedAttrAccessor:
Enabled: true
Style/CaseLikeIf:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
@@ -205,15 +109,6 @@ Style/Documentation:
Style/DoubleNegation:
Enabled: true
Style/ExpandPathArguments:
Enabled: false
Style/ExponentialNotation:
Enabled: true
Style/FormatString:
Enabled: false
Style/FormatStringToken:
Enabled: false
@@ -223,33 +118,9 @@ Style/FrozenStringLiteralComment:
Style/GuardClause:
Enabled: false
Style/HashAsLastArrayItem:
Enabled: false
Style/HashEachMethods:
Enabled: true
Style/HashLikeCase:
Enabled: true
Style/HashTransformKeys:
Enabled: true
Style/HashTransformValues:
Enabled: false
Style/IfUnlessModifier:
Enabled: false
Style/InverseMethods:
Enabled: false
Style/Lambda:
Enabled: false
Style/MutableConstant:
Enabled: false
Style/PercentLiteralDelimiters:
PreferredDelimiters:
'%i': '()'
@@ -258,36 +129,9 @@ Style/PercentLiteralDelimiters:
Style/PerlBackrefs:
AutoCorrect: false
Style/RedundantAssignment:
Enabled: false
Style/RedundantFetchBlock:
Enabled: true
Style/RedundantFileExtensionInRequire:
Enabled: true
Style/RedundantRegexpCharacterClass:
Enabled: false
Style/RedundantRegexpEscape:
Enabled: false
Style/RedundantReturn:
Enabled: true
Style/RegexpLiteral:
Enabled: false
Style/RescueStandardError:
Enabled: false
Style/SignalException:
Enabled: false
Style/SlicingWithRange:
Enabled: true
Style/SymbolArray:
Enabled: false
@@ -296,6 +140,3 @@ Style/TrailingCommaInArrayLiteral:
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: 'comma'
Style/UnpackFirst:
Enabled: false

View File

@@ -1 +1 @@
2.7.2
2.6.6

View File

@@ -5,39 +5,38 @@ Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
and provided thanks to the work of the following contributors:
* [Gargron](https://github.com/Gargron)
* [ThibG](https://github.com/ThibG)
* [dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [ThibG](https://github.com/ThibG)
* [ykzts](https://github.com/ykzts)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [akihikodaki](https://github.com/akihikodaki)
* [mjankowski](https://github.com/mjankowski)
* [unarist](https://github.com/unarist)
* [yiskah](https://github.com/yiskah)
* [nolanlawson](https://github.com/nolanlawson)
* [abcang](https://github.com/abcang)
* [mayaeh](https://github.com/mayaeh)
* [ysksn](https://github.com/ysksn)
* [mayaeh](https://github.com/mayaeh)
* [sorin-davidoi](https://github.com/sorin-davidoi)
* [noellabo](https://github.com/noellabo)
* [lynlynlynx](https://github.com/lynlynlynx)
* [m4sk1n](mailto:me@m4sk.in)
* [Marcin Mikołajczak](mailto:me@m4sk.in)
* [Kjwon15](https://github.com/Kjwon15)
* [noellabo](https://github.com/noellabo)
* [renatolond](https://github.com/renatolond)
* [alpaca-tc](https://github.com/alpaca-tc)
* [jeroenpraat](https://github.com/jeroenpraat)
* [nclm](https://github.com/nclm)
* [ineffyble](https://github.com/ineffyble)
* [zunda](https://github.com/zunda)
* [shleeable](https://github.com/shleeable)
* [zunda](https://github.com/zunda)
* [Masoud Abkenar](mailto:ampbox@gmail.com)
* [blackle](https://github.com/blackle)
* [Quent-in](https://github.com/Quent-in)
* [JantsoP](https://github.com/JantsoP)
* [nullkal](https://github.com/nullkal)
* [yookoala](https://github.com/yookoala)
* [Brawaru](https://github.com/Brawaru)
* [ariasuni](https://github.com/ariasuni)
* [Sasha-Sorokin](https://github.com/Sasha-Sorokin)
* [Aditoo17](https://github.com/Aditoo17)
* [Quenty31](https://github.com/Quenty31)
* [marek-lach](https://github.com/marek-lach)
@@ -46,9 +45,9 @@ and provided thanks to the work of the following contributors:
* [danhunsaker](https://github.com/danhunsaker)
* [eramdam](https://github.com/eramdam)
* [takayamaki](https://github.com/takayamaki)
* [ariasuni](https://github.com/ariasuni)
* [masarakki](https://github.com/masarakki)
* [ticky](https://github.com/ticky)
* [trwnh](https://github.com/trwnh)
* [ThisIsMissEm](https://github.com/ThisIsMissEm)
* [hinaloe](https://github.com/hinaloe)
* [hcmiya](https://github.com/hcmiya)
@@ -58,10 +57,10 @@ and provided thanks to the work of the following contributors:
* [yukimochi](https://github.com/yukimochi)
* [palindromordnilap](https://github.com/palindromordnilap)
* [rkarabut](https://github.com/rkarabut)
* [trwnh](https://github.com/trwnh)
* [nightpool](https://github.com/nightpool)
* [Artoria2e5](https://github.com/Artoria2e5)
* [marrus-sh](https://github.com/marrus-sh)
* [dunn](https://github.com/dunn)
* [krainboltgreene](https://github.com/krainboltgreene)
* [pfigel](https://github.com/pfigel)
* [BoFFire](https://github.com/BoFFire)
@@ -85,25 +84,25 @@ and provided thanks to the work of the following contributors:
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [yhirano55](https://github.com/yhirano55)
* [rinsuki](https://github.com/rinsuki)
* [dunn](https://github.com/dunn)
* [devkral](https://github.com/devkral)
* [camponez](https://github.com/camponez)
* [hugogameiro](https://github.com/hugogameiro)
* [SerCom_KC](mailto:szescxz@gmail.com)
* [aschmitz](https://github.com/aschmitz)
* [mfmfuyu](https://github.com/mfmfuyu)
* [kedamaDQ](https://github.com/kedamaDQ)
* [fpiesche](https://github.com/fpiesche)
* [gandaro](https://github.com/gandaro)
* [johnsudaar](https://github.com/johnsudaar)
* [trebmuh](https://github.com/trebmuh)
* [rmhasan](https://github.com/rmhasan)
* [kedamaDQ](https://github.com/kedamaDQ)
* [lindwurm](https://github.com/lindwurm)
* [victorhck](mailto:victorhck@geeko.site)
* [voidsatisfaction](https://github.com/voidsatisfaction)
* [mkljczk](https://github.com/mkljczk)
* [hikari-no-yume](https://github.com/hikari-no-yume)
* [seefood](https://github.com/seefood)
* [jackjennings](https://github.com/jackjennings)
* [mfmfuyu](https://github.com/mfmfuyu)
* [puckipedia](https://github.com/puckipedia)
* [spla](mailto:spla@mastodont.cat)
* [walf443](https://github.com/walf443)
@@ -112,15 +111,14 @@ and provided thanks to the work of the following contributors:
* [Ashley](mailto:expenses@airmail.cc)
* [xqus](https://github.com/xqus)
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
* [fakenine](https://github.com/fakenine)
* [Samy KACIMI](mailto:samy.kacimi@gmail.com)
* [tsuwatch](https://github.com/tsuwatch)
* [victorhck](https://github.com/victorhck)
* [mkljczk](https://github.com/mkljczk)
* [manuelviens](https://github.com/manuelviens)
* [tateisu](https://github.com/tateisu)
* [fvh-P](https://github.com/fvh-P)
* [rtucker](https://github.com/rtucker)
* [Anna e só](mailto:contraexemplos@gmail.com)
* [dariusk](https://github.com/dariusk)
* [kazu9su](https://github.com/kazu9su)
* [Komic](https://github.com/Komic)
* [lmorchard](https://github.com/lmorchard)
@@ -147,9 +145,9 @@ and provided thanks to the work of the following contributors:
* [fhemberger](https://github.com/fhemberger)
* [Gomasy](https://github.com/Gomasy)
* [greysteil](https://github.com/greysteil)
* [hendotcat](https://github.com/hendotcat)
* [hencatsmith](https://github.com/hencatsmith)
* [d6rkaiz](https://github.com/d6rkaiz)
* [ladyisatis](https://github.com/ladyisatis)
* [Reverite](https://github.com/Reverite)
* [JohnD28](https://github.com/JohnD28)
* [znz](https://github.com/znz)
* [saper](https://github.com/saper)
@@ -162,14 +160,14 @@ and provided thanks to the work of the following contributors:
* [leopku](https://github.com/leopku)
* [SansPseudoFix](https://github.com/SansPseudoFix)
* [spla](mailto:sp@mastodont.cat)
* [tateisu](https://github.com/tateisu)
* [tomfhowe](https://github.com/tomfhowe)
* [noraworld](https://github.com/noraworld)
* [lfuelling](https://github.com/lfuelling)
* [aji-su](https://github.com/aji-su)
* [theboss](https://github.com/theboss)
* [nzws](https://github.com/nzws)
* [duxovni](https://github.com/duxovni)
* [smorimoto](https://github.com/smorimoto)
* [mashirozx](https://github.com/mashirozx)
* [178inaba](https://github.com/178inaba)
* [acid-chicken](https://github.com/acid-chicken)
* [xgess](https://github.com/xgess)
@@ -177,6 +175,7 @@ and provided thanks to the work of the following contributors:
* [aablinov](https://github.com/aablinov)
* [stalker314314](https://github.com/stalker314314)
* [cutls](https://github.com/cutls)
* [dariusk](https://github.com/dariusk)
* [huertanix](https://github.com/huertanix)
* [eleboucher](https://github.com/eleboucher)
* [halkeye](https://github.com/halkeye)
@@ -184,7 +183,7 @@ and provided thanks to the work of the following contributors:
* [treby](https://github.com/treby)
* [jpdevries](https://github.com/jpdevries)
* [gdpelican](https://github.com/gdpelican)
* [Korbinian](mailto:kontakt@korbinian-michl.de)
* [kmichl](https://github.com/kmichl)
* [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
* [panarom](https://github.com/panarom)
* [Dar13](https://github.com/Dar13)
@@ -226,7 +225,6 @@ and provided thanks to the work of the following contributors:
* [aaribaud](https://github.com/aaribaud)
* [pointlessone](https://github.com/pointlessone)
* [Andrew](mailto:andrewlchronister@gmail.com)
* [arielrodrigues](https://github.com/arielrodrigues)
* [aurelien-reeves](https://github.com/aurelien-reeves)
* [elegaanz](https://github.com/elegaanz)
* [estuans](https://github.com/estuans)
@@ -240,7 +238,6 @@ and provided thanks to the work of the following contributors:
* [muffinista](https://github.com/muffinista)
* [cdutson](https://github.com/cdutson)
* [farlistener](https://github.com/farlistener)
* [divergentdave](https://github.com/divergentdave)
* [DavidLibeau](https://github.com/DavidLibeau)
* [dmerejkowsky](https://github.com/dmerejkowsky)
* [ddevault](https://github.com/ddevault)
@@ -279,7 +276,7 @@ and provided thanks to the work of the following contributors:
* [xPaw](https://github.com/xPaw)
* [petzah](https://github.com/petzah)
* [ignisf](https://github.com/ignisf)
* [lumenwrites](https://github.com/lumenwrites)
* [raymestalez](https://github.com/raymestalez)
* [remram44](https://github.com/remram44)
* [sts10](https://github.com/sts10)
* [SuperSandro2000](https://github.com/SuperSandro2000)
@@ -289,9 +286,8 @@ and provided thanks to the work of the following contributors:
* [Sir-Boops](https://github.com/Sir-Boops)
* [stemid](https://github.com/stemid)
* [sumdog](https://github.com/sumdog)
* [OmmyZhang](https://github.com/OmmyZhang)
* [ThomasLeister](https://github.com/ThomasLeister)
* [Tom McAtee](mailto:a1608768@student.adelaide.edu.au)
* [mcat-ee](https://github.com/mcat-ee)
* [tototoshi](https://github.com/tototoshi)
* [TrashMacNugget](https://github.com/TrashMacNugget)
* [VirtuBox](https://github.com/VirtuBox)
@@ -318,13 +314,11 @@ and provided thanks to the work of the following contributors:
* [matsurai25](https://github.com/matsurai25)
* [mecab](https://github.com/mecab)
* [nicobz25](https://github.com/nicobz25)
* [niwatori24](https://github.com/niwatori24)
* [oliverkeeble](https://github.com/oliverkeeble)
* [partev](https://github.com/partev)
* [pinfort](https://github.com/pinfort)
* [rbaumert](https://github.com/rbaumert)
* [rhoio](https://github.com/rhoio)
* [santiagorodriguez96](https://github.com/santiagorodriguez96)
* [sclaire-1](https://github.com/sclaire-1)
* [umonaca](https://github.com/umonaca)
* [usagi-f](https://github.com/usagi-f)
@@ -333,7 +327,7 @@ and provided thanks to the work of the following contributors:
* [wxcafe](https://github.com/wxcafe)
* [Grawl](https://github.com/Grawl)
* [新都心(Neet Shin)](mailto:nucx@dio-vox.com)
* [clarfonthey](https://github.com/clarfonthey)
* [clarfon](https://github.com/clarfon)
* [cygnan](https://github.com/cygnan)
* [Awea](https://github.com/Awea)
* [eai04191](https://github.com/eai04191)
@@ -364,11 +358,11 @@ and provided thanks to the work of the following contributors:
* [schas002](https://github.com/schas002)
* [contraexemplo](https://github.com/contraexemplo)
* [abackstrom](https://github.com/abackstrom)
* [arielrodrigues](https://github.com/arielrodrigues)
* [orlea](https://github.com/orlea)
* [armandfardeau](https://github.com/armandfardeau)
* [raboof](https://github.com/raboof)
* [jumbosushi](https://github.com/jumbosushi)
* [acuteaura](https://github.com/acuteaura)
* [ayumin](https://github.com/ayumin)
* [bzg](https://github.com/bzg)
* [BastienDurel](https://github.com/BastienDurel)
@@ -395,7 +389,7 @@ and provided thanks to the work of the following contributors:
* [colindean](https://github.com/colindean)
* [DeeUnderscore](https://github.com/DeeUnderscore)
* [dachinat](https://github.com/dachinat)
* [monsterpit-firedemon](https://github.com/monsterpit-firedemon)
* [shapeshifter-system](https://github.com/shapeshifter-system)
* [watilde](https://github.com/watilde)
* [daprice](https://github.com/daprice)
* [da2x](https://github.com/da2x)
@@ -406,13 +400,14 @@ and provided thanks to the work of the following contributors:
* [singingwolfboy](https://github.com/singingwolfboy)
* [caldwell](https://github.com/caldwell)
* [davidcelis](https://github.com/davidcelis)
* [divergentdave](https://github.com/divergentdave)
* [davefp](https://github.com/davefp)
* [yipdw](https://github.com/yipdw)
* [debanshuk](https://github.com/debanshuk)
* [mascali33](https://github.com/mascali33)
* [DerekNonGeneric](https://github.com/DerekNonGeneric)
* [dblandin](https://github.com/dblandin)
* [Aranaur](https://github.com/Aranaur)
* [Drew Gates](mailto:aranaur@users.noreply.github.com)
* [dtschust](https://github.com/dtschust)
* [Dryusdan](https://github.com/Dryusdan)
* [d3vgru](https://github.com/d3vgru)
@@ -456,25 +451,22 @@ and provided thanks to the work of the following contributors:
* [J Yeary](mailto:usbsnowcrash@users.noreply.github.com)
* [jack-michaud](https://github.com/jack-michaud)
* [Floppy](https://github.com/Floppy)
* [Jarek Lipski](mailto:pub@loomchild.net)
* [Jennifer Glauche](mailto:=^.^=@github19.jglauche.de)
* [Jennifer Kruse](mailto:jenkr55@gmail.com)
* [Jeremy Rose](mailto:nornagon@nornagon.net)
* [Jessica](mailto:46502909+hyenagirl64@users.noreply.github.com)
* [Jessica K. Litwin](mailto:jessica@litw.in)
* [Jo Decker](mailto:trolldecker@users.noreply.github.com)
* [Joan Montané](mailto:jmontane@users.noreply.github.com)
* [loomchild](https://github.com/loomchild)
* [jglauche](https://github.com/jglauche)
* [jenkr55](https://github.com/jenkr55)
* [hyenagirl64](https://github.com/hyenagirl64)
* [press5](https://github.com/press5)
* [TrollDecker](https://github.com/TrollDecker)
* [jmontane](https://github.com/jmontane)
* [Jonathan Klee](mailto:klee.jonathan@gmail.com)
* [Jordan Guerder](mailto:jguerder@fr.pulseheberg.net)
* [Joseph Mingrone](mailto:jehops@users.noreply.github.com)
* [Josh Leeb-du Toit](mailto:mail@joshleeb.com)
* [Joshua Wood](mailto:josh@joshuawood.net)
* [Julien](mailto:tiwy57@users.noreply.github.com)
* [Julien Deswaef](mailto:juego@requiem4tv.com)
* [June Sallou](mailto:jnsll@users.noreply.github.com)
* [Jérémy Benoist](mailto:j0k3r@users.noreply.github.com)
* [KEINOS](mailto:github@keinos.com)
* [Kairui Song | 宋恺睿](mailto:ryncsn@gmail.com)
* [Keiji Matsuzaki](mailto:futoase@gmail.com)
* [Kevin Liu](mailto:kevin@potatofrom.space)
* [Kit Redgrave](mailto:qwertyitis@gmail.com)
@@ -490,6 +482,7 @@ and provided thanks to the work of the following contributors:
* [Lukas Burk](mailto:jemus42@users.noreply.github.com)
* [Manato Kameya](mailto:grabacr07+github@gmail.com)
* [Mantas](mailto:mistermantas@users.noreply.github.com)
* [Marcin Mikołajczak](mailto:me@mkljczk.pl)
* [Mareena Kunjachan](mailto:mareenakunjachan@gmail.com)
* [Marek Lach](mailto:marek.brohatwack.lach@gmail.com)
* [Markus R](mailto:wirehack7@users.noreply.github.com)
@@ -536,12 +529,10 @@ and provided thanks to the work of the following contributors:
* [Norayr Chilingarian](mailto:norayr@arnet.am)
* [Noëlle Anthony](mailto:noelle.d.anthony@gmail.com)
* [N氏](mailto:uenok.htc@gmail.com)
* [OSAMU SATO](mailto:satosamu@gmail.com)
* [Olivier Nicole](mailto:olivierthnicole@gmail.com)
* [Oskari Noppa](mailto:noppa@users.noreply.github.com)
* [Otakan](mailto:otakan951@gmail.com)
* [Padraig Fahy](mailto:tech@padraigfahy.com)
* [Patrice Ferlet](mailto:metal3d@gmail.com)
* [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com)
* [Paul](mailto:naydex.mc+github@gmail.com)
* [Pete Keen](mailto:pete@petekeen.net)
@@ -583,6 +574,7 @@ and provided thanks to the work of the following contributors:
* [TakesxiSximada](mailto:takesxi.sximada@gmail.com)
* [Tao Bror Bojlén](mailto:brortao@users.noreply.github.com)
* [Taras Gogol](mailto:taras2358@gmail.com)
* [Tdxdxoz](mailto:tdxdxoz@gmail.com)
* [TheInventrix](mailto:theinventrix@users.noreply.github.com)
* [TheMainOne](mailto:50847364+theevilskeleton@users.noreply.github.com)
* [Thomas Alberola](mailto:thomas@needacoffee.fr)
@@ -602,7 +594,6 @@ and provided thanks to the work of the following contributors:
* [Wesley Ellis](mailto:tahnok@gmail.com)
* [Wiktor](mailto:wiktor@metacode.biz)
* [Wonderfall](mailto:wonderfall@schrodinger.io)
* [Y.Yamashiro](mailto:shukukei@mojizuri.jp)
* [YDrogen](mailto:ydrogen45@gmail.com)
* [YMHuang](mailto:ymhuang@fmbase.tw)
* [YOSHIOKA Eiichiro](mailto:yoshioka.eiichiro@gmail.com)
@@ -647,7 +638,6 @@ and provided thanks to the work of the following contributors:
* [jumoru](mailto:jumoru@mailbox.org)
* [kaiyou](mailto:pierre@jaury.eu)
* [karlyeurl](mailto:karl.yeurl@gmail.com)
* [kawaguchi](mailto:jiikko@users.noreply.github.com)
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
* [kuro5hin](mailto:rusty@kuro5hin.org)
* [leo60228](mailto:leo@60228.dev)
@@ -665,7 +655,6 @@ and provided thanks to the work of the following contributors:
* [notozeki](mailto:notozeki@users.noreply.github.com)
* [ntl-purism](mailto:57806346+ntl-purism@users.noreply.github.com)
* [nzws](mailto:git-yuzu@svk.jp)
* [proxy](mailto:51172302+3n-k1@users.noreply.github.com)
* [rch850](mailto:rich850@gmail.com)
* [roikale](mailto:roikale@users.noreply.github.com)
* [rysiekpl](mailto:rysiek@hackerspace.pl)
@@ -705,414 +694,308 @@ This document is provided for informational purposes only. Since it is only upda
Following people have contributed to translation of Mastodon:
- ᏦᏁᎢᎵᏫ 😷 (KNTRO) (*Spanish, Argentina*)
- Sveinn í Felli (sveinki) (*Icelandic*)
- qezwan (*Persian, Sorani (Kurdish)*)
- Hồ Nhất Duy (kantcer) (*Vietnamese*)
- ᏦᏁᎢᎵᏫ 😷 (*Spanish, Argentina*)
- Sveinn í Felli (*Icelandic*)
- taicv (*Vietnamese*)
- Zoltán Gera (gerazo) (*Hungarian*)
- ButterflyOfFire (BoFFire) (*French, Arabic, Kabyle*)
- adrmzz (*Sardinian*)
- Ramdziana F Y (rafeyu) (*Indonesian*)
- Evert Prants (IcyDiamond) (*Estonian*)
- Daniele Lira Mereb (danilmereb) (*Portuguese, Brazilian*)
- Xosé M. (XoseM) (*Spanish, Galician*)
- Kristijan Tkalec (lapor) (*Slovenian*)
- stan ionut (stanionut12) (*Romanian*)
- Besnik_b (*Albanian*)
- Emanuel Pina (emanuelpina) (*Portuguese*)
- Thai Localization (thl10n) (*Thai*)
- 奈卜拉 (nebula_moe) (*Chinese Simplified*)
- Jeong Arm (Kjwon15) (*Japanese, Korean, Esperanto*)
- Michal Stanke (mstanke) (*Czech*)
- Alix Rossi (palindromordnilap) (*French, Corsican*)
- spla (*Spanish, Catalan*)
- Imre Kristoffer Eilertsen (DandelionSprout) (*Norwegian*)
- Jeroen (jeroenpraat) (*Dutch*)
- ButterflyOfFire (*Arabic; French; Kabyle*)
- Duy (*Vietnamese*)
- Evert Prants (*Estonian*)
- Zoltán Gera (*Hungarian*)
- Daniele Lira Mereb (*Portuguese, Brazilian*)
- Kristijan Tkalec (*Slovenian*)
- stan ionut (*Romanian*)
- Ramdziana F Y (*Indonesian*)
- Michal Stanke (*Czech*)
- Xosé M. (*Galician; Spanish*)
- 奈卜拉 (*Chinese Simplified*)
- borys_sh (*Ukrainian*)
- Miguel Mayol (mitcoes) (*Spanish, Catalan*)
- Danial Behzadi (danialbehzadi) (*Persian*)
- yeft (*Chinese Traditional, Chinese Traditional, Hong Kong*)
- koyu (*German*)
- Koala Yeung (yookoala) (*Chinese Traditional, Hong Kong*)
- Miguel Mayol (*Spanish; Catalan*)
- Besnik_b (*Albanian*)
- Thai Localization (*Thai*)
- Emanuel Pina (*Portuguese*)
- Jeong Arm (*Korean; Esperanto; Japanese*)
- Imre Kristoffer Eilertsen (*Norwegian*)
- Danial Behzadi (*Persian*)
- Osoitz (*Basque*)
- Peterandre (*Norwegian, Norwegian Nynorsk*)
- tzium (*Sardinian*)
- Iváns (Ivans_translator) (*Galician*)
- Sasha Sorokin (Sasha-Sorokin) (*French, Catalan, Danish, German, Greek, Hungarian, Armenian, Korean, Russian, Albanian, Swedish, Ukrainian, Vietnamese, Galician*)
- kamee (*Armenian*)
- tolstoevsky (*Russian*)
- Peterandre (*Norwegian Nynorsk; Norwegian*)
- Jeroen (*Dutch*)
- spla (*Catalan; Spanish*)
- Iváns (*Galician*)
- koyu (*German*)
- Sasha Sorokin (*Russian; Vietnamese; Swedish; Catalan; Greek; Hungarian; Armenian; Albanian; Galician; French; Danish; German; Korean; Ukrainian*)
- enolp (*Asturian*)
- FédiQuébec (manuelviens) (*French*)
- Masoud Abkenar (*Persian*)
- lamnatos (*Greek*)
- Maya Minatsuki (mayaeh) (*Japanese*)
- Masoud Abkenar (mabkenar) (*Persian*)
- Alessandro Levati (Oct326) (*Italian*)
- Alix Rossi (*Corsican; French*)
- arshat (*Kazakh*)
- Roboron (*Spanish*)
- ariasuni (*French, Arabic, Czech, German, Greek, Hungarian, Slovenian, Ukrainian, Chinese Simplified, Portuguese, Brazilian, Persian, Norwegian Nynorsk, Esperanto, Breton, Corsican, Sardinian, Kabyle*)
- Ali Demirtaş (alidemirtas) (*Turkish*)
- Em St Cenydd (cancennau) (*Welsh*)
- Marek Ľach (mareklach) (*Polish, Slovak*)
- Muha Aliss (muhaaliss) (*Turkish*)
- Jurica (ahjk) (*Croatian*)
- FédiQuébec (*French*)
- Marek Ľach (*Slovak; Polish*)
- Muha Aliss (*Turkish*)
- tolstoevsky (*Russian*)
- Emyn-Russell Nt Nefydd (*Welsh*)
- Aditoo17 (*Czech*)
- Maya Minatsuki (*Japanese*)
- ariasuni (*French; Esperanto*)
- Roboron (*Spanish*)
- Alessandro Levati (*Italian*)
- Diluns (*Occitan*)
- gagik_ (*Armenian*)
- vishnuvaratharajan (*Tamil*)
- Marcin Mikołajczak (mkljczkk) (*Czech, Polish, Russian*)
- regulartranslator (*Portuguese, Brazilian*)
- Akarshan Biswas (biswasab) (*Bengali, Sanskrit*)
- Yi-Jyun Pan (pan93412) (*Chinese Traditional*)
- vishnuvaratharajan (*Tamil*)
- Marcin Mikołajczak (*Polish*)
- Yi-Jyun Pan (*Chinese Traditional*)
- adrmzz (*Sardinian*)
- d5Ziif3K (*Ukrainian*)
- GiorgioHerbie (*Italian*)
- Rafael H L Moretti (Moretti) (*Portuguese, Brazilian*)
- Saederup92 (*Danish*)
- christalleras (*Norwegian Nynorsk*)
- cybergene (cyber-gene) (*Japanese*)
- Taloran (*Norwegian Nynorsk*)
- ThibG (*French, Icelandic*)
- xatier (*Chinese Traditional*)
- otrapersona (*Spanish, Spanish, Mexico*)
- ThibG (*French; Icelandic*)
- Akarshan Biswas (*Bengali*)
- atarashiako (*Chinese Simplified*)
- 101010 (101010pl) (*Polish*)
- 101010 (*Polish*)
- silkevicious (*Italian*)
- Floxu (fredrikdim1) (*Norwegian Nynorsk*)
- Bertil Hedkvist (Berrahed) (*Swedish*)
- William(ѕ)ⁿ (wmlgr) (*Spanish*)
- Bertil Hedkvist (*Swedish*)
- cybergene (*Japanese*)
- norayr (*Armenian*)
- Tiago Epifânio (tfve) (*Portuguese*)
- Ryo (DrRyo) (*Korean*)
- Mentor Gashi (mentorgashi.com) (*Albanian*)
- Jaz-Michael King (jazmichaelking) (*Welsh*)
- William(ѕ)ⁿ (*Spanish*)
- Tiago Epifânio (*Portuguese*)
- Mentor Gashi (*Albanian*)
- Jaz-Michael King (*Welsh*)
- carolinagiorno (*Portuguese, Brazilian*)
- Roby Thomas (roby.thomas) (*Malayalam*)
- Bharat Kumar (Marwari) (*Hindi*)
- ThonyVezbe (*Breton*)
- dkdarshan760 (*Sanskrit*)
- Tagomago (tagomago) (*French, Spanish*)
- Roby Thomas (*Malayalam*)
- Bharat Kumar (*Hindi*)
- tykayn (*French*)
- axi (*Finnish*)
- Selyan Slimane AMIRI (slimane_AMIRI) (*Kabyle*)
- Balázs Meskó (mesko.balazs) (*Hungarian*)
- Selyan Slimane AMIRI (*Kabyle*)
- taoxvx (*Danish*)
- Hrach Mkrtchyan (mhrach87) (*Armenian*)
- sabri (thetomatoisavegetable) (*Spanish, Spanish, Argentina*)
- Dewi (Unkorneg) (*French, Breton*)
- Coelacanthus (*Chinese Simplified*)
- syncopams (*Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong*)
- Hrach Mkrtchyan (*Armenian*)
- sabri (*Spanish; Spanish, Argentina*)
- Dewi (*Breton; French*)
- SteinarK (*Norwegian Nynorsk*)
- Sokratis Alichanidis (alichani) (*Greek*)
- Mathias B. Vagnes (vagnes) (*Norwegian*)
- dashersyed (*Urdu (Pakistan)*)
- Acolyte (666noob404) (*Ukrainian*)
- Conight Wang (xfddwhh) (*Chinese Simplified*)
- liffon (*Swedish*)
- Damjan Dimitrioski (gnud) (*Macedonian*)
- Mathias B. Vagnes (*Norwegian*)
- dashersyed (*Urdu*)
- ThonyVezbe (*Breton*)
- Acolyte (*Ukrainian*)
- Conight Wang (*Chinese Simplified*)
- Damjan Dimitrioski (*Macedonian*)
- PPNplus (*Thai*)
- Tagomago (*Spanish; French*)
- shioko (*Chinese Simplified*)
- v4vachan (*Malayalam*)
- Hakim Oubouali (zenata1) (*Standard Moroccan Tamazight*)
- Evgeny Petrov (kondra007) (*Russian*)
- Gwenn (Belvar) (*Breton*)
- StanleyFrew (*French*)
- Hayk Khachatryan (brutusromanus123) (*Armenian*)
- Balázs Meskó (*Hungarian*)
- Evgeny Petrov (*Russian*)
- Gwenn (*Breton*)
- Ryo (*Korean*)
- Rafael H L Moretti (*Portuguese, Brazilian*)
- jaranta (*Finnish*)
- Felicia (midsommar) (*Swedish*)
- Denys (dector) (*Ukrainian*)
- Pukima (pukimaaa) (*German*)
- Vanege (*Esperanto*)
- Jess Rafn (therealyez) (*Danish*)
- strubbl (*German*)
- Stasiek Michalski (hellcp) (*Polish*)
- gagik_ (*Armenian*)
- Felicia (*Swedish*)
- Jess Rafn (*Danish*)
- Stasiek Michalski (*Polish*)
- liffon (*Swedish*)
- dxwc (*Bengali*)
- Saederup92 (*Danish*)
- Vanege (*Esperanto*)
- jmontane (*Catalan*)
- Liboide (*Spanish*)
- Johan Schiff (schyffel) (*Swedish*)
- Arunmozhi (tecoholic) (*Tamil*)
- kat (katktv) (*Russian, Ukrainian*)
- Rikard Linde (rikardlinde) (*Swedish*)
- oti4500 (*Hungarian, Ukrainian*)
- Laura (selfisekai) (*Polish*)
- Rachida S. (ZiriSut) (*Kabyle*)
- diazepan (*Spanish, Spanish, Argentina*)
- Johan Schiff (*Swedish*)
- Arunmozhi (*Tamil*)
- kat (*Ukrainian; Russian*)
- Laura (*Polish*)
- oti4500 (*Hungarian; Ukrainian*)
- diazepan (*Spanish; Spanish, Argentina*)
- Sokratis Alichanidis (*Greek*)
- Rikard Linde (*Swedish*)
- Juan José Salvador Piedra (*Spanish*)
- marzuquccen (*Kabyle*)
- Juan José Salvador Piedra (JuanjoSalvador) (*Spanish*)
- Tigran (tigransimonyan) (*Armenian*)
- BurekzFinezt (*Serbian (Cyrillic)*)
- BurekzFinezt (*Serbian*)
- SHeija (*Finnish*)
- atriix (*Swedish*)
- Jack R (isaac.97_WT) (*Spanish*)
- antonyho (*Chinese Traditional, Hong Kong*)
- andruhov (*Russian, Ukrainian*)
- Aryamik Sharma (Aryamik) (*Swedish, Hindi*)
- phena109 (*Chinese Traditional, Hong Kong*)
- 森の子リスのミーコの大冒険 (Phroneris) (*Japanese*)
- るいーね (ruine) (*Japanese*)
- ahangarha (*Persian*)
- Sam Tux (imahbub) (*Bengali*)
- igordrozniak (*Polish*)
- Jack R (*Spanish*)
- andruhov (*Ukrainian; Russian*)
- 森の子リスのミーコの大冒険 (*Japanese*)
- るいーね (*Japanese*)
- Sam Tux (*Bengali*)
- Unmual (*Spanish*)
- Isaac Huang (caasih) (*Chinese Traditional*)
- AW Unad (awcodify) (*Indonesian*)
- Allen Zhong (AstroProfundis) (*Chinese Simplified*)
- Cutls (cutls) (*Japanese*)
- Ray (Ipsumry) (*Spanish*)
- Falling Snowdin (tghgg) (*Vietnamese*)
- coxde (*Chinese Simplified*)
- Rasmus Lindroth (RasmusLindroth) (*Swedish*)
- Andrea Lo Iacono (niels0n) (*Italian*)
- Kinshuk Sunil (kinshuksunil) (*Hindi*)
- Ullas Joseph (ullasjoseph) (*Malayalam*)
- Goudarz Jafari (Goudarz) (*Persian*)
- Yu-Pai Liu (tedliou) (*Chinese Traditional*)
- Amarin Cemthong (acitmaster) (*Thai*)
- juanda097 (juanda-097) (*Spanish*)
- AW Unad (*Indonesian*)
- Cutls (*Japanese*)
- Ray (*Spanish*)
- Falling Snowdin (*Vietnamese*)
- Andrea Lo Iacono (*Italian*)
- EPEMA (*German*)
- Kinshuk Sunil (*Hindi*)
- Ullas Joseph (*Malayalam*)
- Yu-Pai Liu (*Chinese Traditional*)
- Amarin Cemthong (*Thai*)
- juanda097 (*Spanish*)
- Anunnakey (*Macedonian*)
- fragola (*Italian*)
- StanleyFrew (*French*)
- erikstl (*Esperanto*)
- twpenguin (*Chinese Traditional*)
- bobchao (*Chinese Traditional*)
- Esther (esthermations) (*Portuguese*)
- MadeInSteak (*Finnish*)
- Heimen Stoffels (vistausss) (*Dutch*)
- Rajarshi Guha (rajarshiguha) (*Bengali*)
- Andrew (iAndrew3) (*Romanian*)
- Gopal Sharma (gopalvirat) (*Hindi*)
- Heimen Stoffels (*Dutch*)
- Rajarshi Guha (*Bengali*)
- Andrew (*Romanian*)
- Goudarz Jafari (*Persian*)
- arethsu (*Swedish*)
- Tofiq Abdula (Xwla) (*Sorani (Kurdish)*)
- Carlos Solís (csolisr) (*Esperanto*)
- Parthan S Ramanujam (parthan) (*Tamil*)
- Kasper Nymand (KasperNymand) (*Danish*)
- TS (morte) (*Finnish*)
- subram (*Turkish*)
- Carlos Solís (*Esperanto*)
- Parthan S Ramanujam (*Tamil*)
- Ali Demirtaş (*Turkish*)
- Kasper Nymand (*Danish*)
- TS (*Finnish*)
- SensDeViata (*Ukrainian*)
- Ptrcmd (ptrcmd) (*Chinese Traditional*)
- SergioFMiranda (*Portuguese, Brazilian*)
- Scvoet (scvoet) (*Chinese Simplified*)
- hiroTS (*Chinese Traditional*)
- johne32rus23 (*Russian*)
- OctolinGamer (*Portuguese, Brazilian*)
- AzureNya (*Chinese Simplified*)
- OctolinGamer (octolingamer) (*Portuguese, Brazilian*)
- Ram varma (ram4varma) (*Tamil*)
- Hexandcube (hexandcube) (*Polish*)
- 北䑓如法 (Nyoho) (*Japanese*)
- Ram varma (*Tamil*)
- 北䑓如法 (*Japanese*)
- frumble (*German*)
- kekkepikkuni (*Tamil*)
- Neo_Chen (NeoChen1024) (*Chinese Traditional*)
- oorsutri (*Tamil*)
- Rhys Harrison (rhedders) (*Esperanto*)
- Nithin V (Nithin896) (*Tamil*)
- Miro Rauhala (mirorauhala) (*Finnish*)
- Nithin V (*Tamil*)
- Miro Rauhala (*Finnish*)
- diorama (*Italian*)
- AlexKoala (alexkoala) (*Korean*)
- Aswin C (officialcjunior) (*Malayalam*)
- Guillaume Turchini (orion78fr) (*French*)
- Ganesh D (auntgd) (*Marathi*)
- Rhys Harrison (*Esperanto*)
- Guillaume Turchini (*French*)
- Ganesh D (*Marathi*)
- dragnucs2 (*Arabic*)
- Ryan Ho (koungho) (*Chinese Traditional*)
- Pedro Henrique (exploronauta) (*Portuguese, Brazilian*)
- Tejas Harad (h_tejas) (*Marathi*)
- Vasanthan (vasanthan) (*Tamil*)
- 硫酸鶏 (acid_chicken) (*Japanese*)
- clarmin b8 (clarminb8) (*Sorani (Kurdish)*)
- Pedro Henrique (*Portuguese, Brazilian*)
- Tejas Harad (*Marathi*)
- Vasanthan (*Tamil*)
- 硫酸鶏 (*Japanese*)
- manukp (*Malayalam*)
- psymyn (*Hebrew*)
- earth dweller (sanethoughtyt) (*Marathi*)
- meijerivoi (toilet) (*Finnish*)
- earth dweller (*Marathi*)
- meijerivoi (*Finnish*)
- essaar (*Tamil*)
- serubeena (*Swedish*)
- Karol Kosek (krkkPL) (*Polish*)
- Rintan (*Japanese*)
- Karol Kosek (*Polish*)
- valarivan (*Tamil*)
- Hernik (hernik27) (*Czech*)
- Sebastián Andil (Selrond) (*Slovak*)
- Hinaloe (hinaloe) (*Japanese*)
- filippodb (*Italian*)
- Sebastián Andil (*Slovak*)
- v4vachan (*Malayalam*)
- KEINOS (*Japanese*)
- Balázs Meskó (meskobalazs) (*Hungarian*)
- Bottle (suryasalem2010) (*Tamil*)
- Ivan T. (*Chinese Traditional, Hong Kong*)
- filippodb (*Italian*)
- Balázs Meskó (*Hungarian*)
- JzshAC (*Chinese Simplified*)
- Wrya ali (John12) (*Sorani (Kurdish)*)
- Khóo (khootiatling) (*Chinese Traditional*)
- Steven Tappert (sammy8806) (*German*)
- Antillion (antillion99) (*Spanish*)
- Pukima (Pukimaa) (*German*)
- Reg3xp (*Persian*)
- hiphipvargas (*Portuguese*)
- Bottle (*Tamil*)
- Khóo (*Chinese Traditional*)
- Steven Tappert (*German*)
- Antillion (*Spanish*)
- ZiriSut (*Kabyle*)
- gowthamanb (*Tamil*)
- Ch. (sftblw) (*Korean*)
- Jeff Huang (s8321414) (*Chinese Traditional*)
- Arttu Ylhävuori (arttu.ylhavuori) (*Finnish*)
- hiphipvargas (*Portuguese*)
- Arttu Ylhävuori (*Finnish*)
- Ch. (*Korean*)
- tctovsli (*Norwegian Nynorsk*)
- Timo Tijhof (Krinkle) (*Dutch*)
- Yamagishi Kazutoshi (ykzts) (*Japanese, Icelandic, Sorani (Kurdish)*)
- Hinaloe (*Japanese*)
- strubbl (*German*)
- vjasiegd (*Polish*)
- SamitiMed (samiti3d) (*Thai*)
- Rekan Adl (rekan-adl1) (*Sorani (Kurdish)*)
- SamitiMed (*Thai*)
- Reg3xp (*Persian*)
- AlexKoala (*Korean*)
- umelard (*Hebrew*)
- Antara2Cinta (Se7enTime) (*Indonesian*)
- VSx86 (*Russian*)
- Daniel Dimitrov (danny-dimitrov) (*Bulgarian*)
- parnikkapore (*Thai*)
- Daniel Dimitrov (*Bulgarian*)
- mynameismonkey (*Welsh*)
- Sherwan Othman (sherwanothman11) (*Sorani (Kurdish)*)
- Yassine Aït-El-Mouden (yaitelmouden) (*Standard Moroccan Tamazight*)
- parnikkapore (*Thai*)
- Mo_der Steven (*Chinese Simplified*)
- SKELET (*Danish*)
- Mo_der Steven (SakuraPuare) (*Chinese Simplified*)
- Fei Yang (Fei1Yang) (*Chinese Traditional*)
- ALEM FARID (faridatcemlulaqbayli) (*Kabyle*)
- Renato "Lond" Cerqueira (*Portuguese, Brazilian*)
- enipra (*Armenian*)
- musix (*Persian*)
- Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*)
- ギャラ (gyara) (*Japanese, Chinese Simplified*)
- Hougo (hougo) (*French*)
- ギャラ (*Chinese Simplified; Japanese*)
- ALEM FARID (*Kabyle*)
- ybardapurkar (*Marathi*)
- Adrián Lattes (haztecaso) (*Spanish*)
- TracyJacks (*Chinese Simplified*)
- Adrián Lattes (*Spanish*)
- rasheedgm (*Kannada*)
- GatoOscuro (*Spanish*)
- mecqor labi (mecqorlabi) (*Persian*)
- Belkacem Mohammed (belkacem77) (*Kabyle*)
- Navjot Singh (nspeaks) (*Hindi*)
- omquylzu (*Latvian*)
- Belkacem Mohammed (*Kabyle*)
- Navjot Singh (*Hindi*)
- Ozai (*German*)
- Sahak Petrosyan (petrosyan) (*Armenian*)
- siamano (*Thai, Esperanto*)
- Viorel-Cătălin Răpițeanu (rapiteanu) (*Romanian*)
- Siddhartha Sarathi Basu (quinoa_biryani) (*Bengali*)
- Pachara Chantawong (pachara2202) (*Thai*)
- mkljczk (*Polish*)
- Skew (noan.perrot) (*French*)
- Zijian Zhao (jobs2512821228) (*Chinese Simplified*)
- Sahak Petrosyan (*Armenian*)
- siamano (*Thai; Esperanto*)
- se7entime (*Indonesian*)
- Viorel-Cătălin Răpițeanu (*Romanian*)
- Siddhartha Sarathi Basu (*Bengali*)
- Pachara Chantawong (*Thai*)
- Skew (*French*)
- Zijian Zhao (*Chinese Simplified*)
- Guru Prasath Anandapadmanaban (*Tamil*)
- turtle836 (*German*)
- Guru Prasath Anandapadmanaban (guruprasath) (*Tamil*)
- Lamin (laminne) (*Japanese*)
- Marcepanek_ (thekingmarcepan) (*Polish*)
- Feruz Oripov (FeruzOripov) (*Russian*)
- Yann Aguettaz (yann-a) (*French*)
- Mick Onio (xgc.redes) (*Asturian*)
- Tianqi Zhang (tina.zhang040609) (*Chinese Simplified*)
- Malik Mann (dermalikmann) (*German*)
- dadosch (*German*)
- r3dsp1 (*Chinese Traditional, Hong Kong*)
- padulafacundo (*Spanish*)
- GatoOscuro (*Spanish*)
- Lamin (*Japanese*)
- Marcepanek_ (*Polish*)
- Yann Aguettaz (*French*)
- Feruz Oripov (*Russian*)
- Mick Onio (*Asturian*)
- hg6 (*Hindi*)
- Orlando Murcio (Atos20) (*Spanish, Mexico*)
- piupiupiudiu (*Chinese Simplified*)
- shdy (*German*)
- Padraic Calpin (padraic-padraic) (*Slovenian*)
- Ильзира Рахматуллина (rahmatullinailzira53) (*Tatar*)
- Malik Mann (*German*)
- padulafacundo (*Spanish*)
- r3dsp1 (*Chinese Traditional, Hong Kong*)
- Tianqi Zhang (*Chinese Simplified*)
- Padraic Calpin (*Slovenian*)
- cenegd (*Chinese Simplified*)
- Hugh Liu (youloveonlymeh) (*Chinese Simplified*)
- Pixelcode (realpixelcode) (*German*)
- Yogesh K S (yogi) (*Kannada*)
- Rakino (rakino) (*Chinese Simplified*)
- Miquel Sabaté Solà (mssola) (*Catalan*)
- piupiupiudiu (*Chinese Simplified*)
- Hugh Liu (*Chinese Simplified*)
- Rakino (*Chinese Simplified*)
- Jothipazhani Nagarajan (*Tamil*)
- Miquel Sabaté Solà (*Catalan*)
- AmazighNM (*Kabyle*)
- Jothipazhani Nagarajan (jothipazhani.n) (*Tamil*)
- Clash Clans (KURD12345) (*Sorani (Kurdish)*)
- Solid Rhino (*Dutch*)
- hallomaurits (*Dutch*)
- alnd hezh (alndhezh) (*Sorani (Kurdish)*)
- Solid Rhino (SolidRhino) (*Dutch*)
- k_taka (peaceroad) (*Japanese*)
- Hallo Abdullah (hallo_hamza12) (*Sorani (Kurdish)*)
- hussama (*Portuguese, Brazilian*)
- Sébastien Feugère (smonff) (*French*)
- 林水溶 (shuiRong) (*Chinese Simplified*)
- eichkat3r (*German*)
- OminousCry (*Russian*)
- SnDer (*Dutch*)
- PifyZ (*French*)
- Tom_ (*Czech*)
- Tagada (Tagadda) (*French*)
- shafouz (*Portuguese, Brazilian*)
- Kahina Mess (K_hina) (*Kabyle*)
- Nathaël Noguès (NatNgs) (*French*)
- Kk (kishorkumara3) (*Kannada*)
- Swati Sani (swatisani) (*Urdu (Pakistan)*)
- Shrinivasan T (tshrinivasan) (*Tamil*)
- さっかりんにーさん (saccharin23) (*Japanese*)
- 夜楓Yoka (Yoka2627) (*Chinese Simplified*)
- Daniel M. (daniconil) (*Catalan*)
- Vikatakavi (*Kannada*)
- Tagada (*French*)
- Tom_ (*Czech*)
- SnDer (*Dutch*)
- eichkat3r (*German*)
- PifyZ (*French*)
- OminousCry (*Russian*)
- Shrinivasan T (*Tamil*)
- Nathaël Noguès (*French*)
- Daniel M. (*Catalan*)
- Swati Sani (*Urdu*)
- Kk (*Kannada*)
- SusVersiva (*Catalan*)
- Tradjincal (tradjincal) (*French*)
- pullopen (*Chinese Simplified*)
- Robin van der Vliet (RobinvanderVliet) (*Esperanto*)
- Robin van der Vliet (*Esperanto*)
- Zinkokooo (*Basque*)
- mmokhi (*Persian*)
- Livingston Samuel (livingston) (*Tamil*)
- Tradjincal (*French*)
- Vikatakavi (*Kannada*)
- prabhjot (*Hindi*)
- twpenguin (*Chinese Traditional*)
- mmokhi (*Persian*)
- sergioaraujo1 (*Portuguese, Brazilian*)
- CyberAmoeba (pseudoobscura) (*Chinese Simplified*)
- Livingston Samuel (*Tamil*)
- tsundoker (*Malayalam*)
- skaaarrr (*German*)
- Ricardo Colin (rysard) (*Spanish*)
- mkljczk (mykylyjczyk) (*Polish*)
- Philipp Fischbeck (PFischbeck) (*German*)
- 夜楓Yoka (*Chinese Simplified*)
- kiwi0 (*Italian*)
- fedot (*Russian*)
- Paz Galindo (paz.almendra.g) (*Spanish*)
- GaggiX (*Italian*)
- mkljczk (*Polish*)
- igordrozniak (*Polish*)
- Ricardo Colin (*Spanish*)
- Esther (*Portuguese*)
- Paz Galindo (*Spanish*)
- Philipp Fischbeck (*German*)
- ralozkolya (*Georgian*)
- Zoé Bőle (zoe1337) (*German*)
- Lukas Fülling (lfuelling) (*German*)
- JackXu (Merman-Jack) (*Chinese Simplified*)
- Aymeric (AymBroussier) (*French*)
- Anoop (anoopp) (*Malayalam*)
- JackXu (*Chinese Simplified*)
- Allen Zhong (*Chinese Simplified*)
- Zoé Bőle (*German*)
- Lukas Fülling (*German*)
- Albatroz Jeremias (*Portuguese*)
- Samir Tighzert (*Kabyle*)
- Nocta (*French*)
- Anoop (*Malayalam*)
- pezcurrel (*Italian*)
- Dremski (*Bulgarian*)
- Xurxo Guerra (xguerrap) (*Galician*)
- mashirozx (*Chinese Simplified*)
- Albatroz Jeremias (albjeremias) (*Portuguese*)
- Samir Tighzert (samir_t7) (*Kabyle*)
- Apple (blackteaovo) (*Chinese Simplified*)
- Nocta (*French*)
- OpenAlgeria (*Arabic*)
- Aymeric (*French*)
- tamaina (*Japanese*)
- abidin toumi (Zet24) (*Arabic*)
- xpac1985 (xpac) (*German*)
- Kaede (kaedech) (*Japanese*)
- ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*)
- Matias Lavik (matiaslavik) (*Norwegian Nynorsk*)
- smedvedev (*Russian*)
- mikel (mikelalas) (*Spanish*)
- Doug (douglasalvespe) (*Portuguese, Brazilian*)
- Trond Boksasp (boksasp) (*Norwegian*)
- Doug (*Portuguese, Brazilian*)
- Matias Lavik (*Norwegian Nynorsk*)
- Fleva (*Sardinian*)
- Mohammad Adnan Mahmood (adnanmig) (*Arabic*)
- Sais Lakshmanan (Saislakshmanan) (*Tamil*)
- Amith Raj Shetty (amithraj1989) (*Kannada*)
- OpenAlgeria (*Arabic*)
- koppe-pan (*Japanese*)
- Amith Raj Shetty (*Kannada*)
- smedvedev (*Russian*)
- Trond Boksasp (*Norwegian*)
- random_person (*Spanish*)
- djoerd (*Dutch*)
- Baban Abdulrahman (baban.abdulrehman) (*Sorani (Kurdish)*)
- ebrezhoneg (*Breton*)
- dashty (*Sorani (Kurdish)*)
- Salh_haji6 (*Sorani (Kurdish)*)
- Amir Kurdo (kuraking202) (*Sorani (Kurdish)*)
- おさ (osapon) (*Japanese*)
- Ranj A Abdulqadir (RanjAhmed) (*Sorani (Kurdish)*)
- umonaca (*Chinese Simplified*)
- Bartek Fijałkowski (brateq) (*Polish*)
- tateisu (*Japanese*)
- centumix (*Japanese*)
- Jari Ronkainen (ronchaine) (*Finnish*)
- Savarín Electrográfico Marmota Intergalactica (herrero.maty) (*Spanish*)
- Torsten Högel (torstenhoegel) (*German*)
- Abijeet Patro (Abijeet) (*Basque*)
- Ács Zoltán (acszoltan111) (*Hungarian*)
- Benjamin Cobb (benjamincobb) (*German*)
- waweic (*German*)
- Aries (orlea) (*Japanese*)
- silverscat_3 (SilversCat) (*Japanese*)
- kavitha129 (*Tamil*)
- dcapillae (*Spanish*)
- SamOak (*Portuguese, Brazilian*)
- capiscuas (*Spanish*)
- NeverMine17 (*Russian*)
- Nithya Mary (nithyamary25) (*Tamil*)
- t_aus_m (*German*)
- dobrado (*Portuguese, Brazilian*)
- Hannah (Aniqueper1) (*Chinese Simplified*)
- Jiniux (*Italian*)
- 于晚霞 (xissshawww) (*Chinese Simplified*)
- Sais Lakshmanan (*Tamil*)
- mikel (*Spanish*)
- Mohammad Adnan Mahmood (*Arabic*)

View File

@@ -5,6 +5,7 @@ libidn11
libidn11-dev
libpq-dev
libprotobuf-dev
libssl-dev
libxdamage1
libxfixes3
protobuf-compiler

View File

@@ -3,208 +3,6 @@ Changelog
All notable changes to this project will be documented in this file.
## [3.3.0] - 2020-12-27
### Added
- **Add hotkeys for audio/video control in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/15158), [Gargron](https://github.com/tootsuite/mastodon/pull/15198))
- `Space` and `k` to toggle playback
- `m` to toggle mute
- `f` to toggle fullscreen
- `j` and `l` to go back and forward by 10 seconds
- `.` and `,` to go back and forward by a frame (video only)
- Add expand/compress button on media modal in web UI ([mashirozx](https://github.com/tootsuite/mastodon/pull/15068), [mashirozx](https://github.com/tootsuite/mastodon/pull/15088), [mashirozx](https://github.com/tootsuite/mastodon/pull/15094))
- Add border around 🕺 emoji in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14769))
- Add border around 🐞 emoji in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14712))
- Add home link to the getting started column when home isn't mounted ([ThibG](https://github.com/tootsuite/mastodon/pull/14707))
- Add option to disable swiping motions across the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13885))
- **Add pop-out player for audio/video in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/14870), [Gargron](https://github.com/tootsuite/mastodon/pull/15157), [Gargron](https://github.com/tootsuite/mastodon/pull/14915), [noellabo](https://github.com/tootsuite/mastodon/pull/15309))
- Continue watching/listening when you scroll away
- Action bar to interact with/open toot from the pop-out player
- Add unread notification markers in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14818), [ThibG](https://github.com/tootsuite/mastodon/pull/14960), [ThibG](https://github.com/tootsuite/mastodon/pull/14954), [noellabo](https://github.com/tootsuite/mastodon/pull/14897), [noellabo](https://github.com/tootsuite/mastodon/pull/14907))
- Add paragraph about browser add-ons when encountering errors in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14801))
- Add import and export for bookmarks ([ThibG](https://github.com/tootsuite/mastodon/pull/14956))
- Add cache buster feature for media files ([Gargron](https://github.com/tootsuite/mastodon/pull/15155))
- If you have a proxy cache in front of object storage, deleted files will persist until the cache expires
- If enabled, cache buster will make a special request to the proxy to signal a cache reset
- Add duration option to the mute function ([aquarla](https://github.com/tootsuite/mastodon/pull/13831))
- Add replies policy option to the list function ([ThibG](https://github.com/tootsuite/mastodon/pull/9205), [trwnh](https://github.com/tootsuite/mastodon/pull/15304))
- Add `og:published_time` OpenGraph tags on toots ([nornagon](https://github.com/tootsuite/mastodon/pull/14865))
- **Add option to be notified when a followed user posts** ([Gargron](https://github.com/tootsuite/mastodon/pull/13546), [ThibG](https://github.com/tootsuite/mastodon/pull/14896), [Gargron](https://github.com/tootsuite/mastodon/pull/14822))
- If you don't want to miss a toot, click the bell button!
- Add client-side validation in password change forms ([ThibG](https://github.com/tootsuite/mastodon/pull/14564))
- Add client-side validation in the registration form ([ThibG](https://github.com/tootsuite/mastodon/pull/14560), [ThibG](https://github.com/tootsuite/mastodon/pull/14599))
- Add support for Gemini URLs ([joshleeb](https://github.com/tootsuite/mastodon/pull/15013))
- Add app shortcuts to web app manifest ([mkljczk](https://github.com/tootsuite/mastodon/pull/15234))
- Add WebAuthn as an alternative 2FA method ([santiagorodriguez96](https://github.com/tootsuite/mastodon/pull/14466), [jiikko](https://github.com/tootsuite/mastodon/pull/14806))
- Add honeypot fields and minimum fill-out time for sign-up form ([ThibG](https://github.com/tootsuite/mastodon/pull/15276))
- Add icon for mutual relationships in relationship manager ([noellabo](https://github.com/tootsuite/mastodon/pull/15149))
- Add follow selected followers button in relationship manager ([noellabo](https://github.com/tootsuite/mastodon/pull/15148))
- **Add subresource integrity for JS and CSS assets** ([Gargron](https://github.com/tootsuite/mastodon/pull/15096))
- If you use a CDN for static assets (JavaScript, CSS, and so on), you have to trust that the CDN does not modify the assets maliciously
- Subresource integrity compares server-generated asset digests with what's actually served from the CDN and prevents such attacks
- Add `ku`, `sa`, `sc`, `zgh` to available locales ([ykzts](https://github.com/tootsuite/mastodon/pull/15138))
- Add ability to force an account to mark media as sensitive ([noellabo](https://github.com/tootsuite/mastodon/pull/14361))
- **Add ability to block access or limit sign-ups from chosen IPs** ([Gargron](https://github.com/tootsuite/mastodon/pull/14963), [ThibG](https://github.com/tootsuite/mastodon/pull/15263))
- Add rules for IPs or CIDR ranges that automatically expire after a configurable amount of time
- Choose the severity of the rule, either blocking all access or merely limiting sign-ups
- **Add support for reversible suspensions through ActivityPub** ([Gargron](https://github.com/tootsuite/mastodon/pull/14989))
- Servers can signal that one of their accounts has been suspended
- During suspension, the account can only delete its own content
- A reversal of the suspension can be signalled the same way
- A local suspension always overrides a remote one
- Add indication to admin UI of whether a report has been forwarded ([ThibG](https://github.com/tootsuite/mastodon/pull/13237))
- Add display of reasons for joining of an account in admin UI ([mashirozx](https://github.com/tootsuite/mastodon/pull/15265))
- Add option to obfuscate domain name in public list of domain blocks ([Gargron](https://github.com/tootsuite/mastodon/pull/15355))
- Add option to make reasons for joining required on sign-up ([ThibG](https://github.com/tootsuite/mastodon/pull/15326), [ThibG](https://github.com/tootsuite/mastodon/pull/15358), [ThibG](https://github.com/tootsuite/mastodon/pull/15385), [ThibG](https://github.com/tootsuite/mastodon/pull/15405))
- Add ActivityPub follower synchronization mechanism ([ThibG](https://github.com/tootsuite/mastodon/pull/14510), [ThibG](https://github.com/tootsuite/mastodon/pull/15026))
- Add outbox attribute to instance actor ([ThibG](https://github.com/tootsuite/mastodon/pull/14721))
- Add featured hashtags as an ActivityPub collection ([Gargron](https://github.com/tootsuite/mastodon/pull/11595), [noellabo](https://github.com/tootsuite/mastodon/pull/15277))
- Add support for dereferencing objects through bearcaps ([Gargron](https://github.com/tootsuite/mastodon/pull/14683), [noellabo](https://github.com/tootsuite/mastodon/pull/14981))
- Add `S3_READ_TIMEOUT` environment variable ([tateisu](https://github.com/tootsuite/mastodon/pull/14952))
- Add `ALLOWED_PRIVATE_ADDRESSES` environment variable ([ThibG](https://github.com/tootsuite/mastodon/pull/14722))
- Add `--fix-permissions` option to `tootctl media remove-orphans` ([Gargron](https://github.com/tootsuite/mastodon/pull/14383), [uist1idrju3i](https://github.com/tootsuite/mastodon/pull/14715))
- Add `tootctl accounts merge` ([Gargron](https://github.com/tootsuite/mastodon/pull/15201), [ThibG](https://github.com/tootsuite/mastodon/pull/15264), [ThibG](https://github.com/tootsuite/mastodon/pull/15256))
- Has someone changed their domain or subdomain thereby creating two accounts where there should be one?
- This command will fix it on your end
- Add `tootctl maintenance fix-duplicates` ([ThibG](https://github.com/tootsuite/mastodon/pull/14860), [Gargron](https://github.com/tootsuite/mastodon/pull/15223), [ThibG](https://github.com/tootsuite/mastodon/pull/15373))
- Index corruption in the database?
- This command is for you
- **Add support for managing multiple stream subscriptions in a single connection** ([Gargron](https://github.com/tootsuite/mastodon/pull/14524), [Gargron](https://github.com/tootsuite/mastodon/pull/14566), [mfmfuyu](https://github.com/tootsuite/mastodon/pull/14859), [zunda](https://github.com/tootsuite/mastodon/pull/14608))
- Previously, getting live updates for multiple timelines required opening a HTTP or WebSocket connection for each
- More connections means more resource consumption on both ends, not to mention the (ever so slight) delay when establishing a new connection
- Now, with just a single WebSocket connection you can subscribe and unsubscribe to and from multiple streams
- Add support for limiting results by both `min_id` and `max_id` at the same time in REST API ([tateisu](https://github.com/tootsuite/mastodon/pull/14776))
- Add `GET /api/v1/accounts/:id/featured_tags` to REST API ([noellabo](https://github.com/tootsuite/mastodon/pull/11817), [noellabo](https://github.com/tootsuite/mastodon/pull/15270))
- Add stoplight for object storage failures, return HTTP 503 in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/13043))
- Add optional `tootctl remove media` cronjob in Helm chart ([dunn](https://github.com/tootsuite/mastodon/pull/14396))
- Add clean error message when `RAILS_ENV` is unset ([ThibG](https://github.com/tootsuite/mastodon/pull/15381))
### Changed
- **Change media modals look in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/15217), [Gargron](https://github.com/tootsuite/mastodon/pull/15221), [Gargron](https://github.com/tootsuite/mastodon/pull/15284), [Gargron](https://github.com/tootsuite/mastodon/pull/15283), [Kjwon15](https://github.com/tootsuite/mastodon/pull/15308), [noellabo](https://github.com/tootsuite/mastodon/pull/15305), [ThibG](https://github.com/tootsuite/mastodon/pull/15417))
- Background of the overlay matches the color of the image
- Action bar to interact with or open the toot from the modal
- Change order of announcements in admin UI to be newest-first ([ThibG](https://github.com/tootsuite/mastodon/pull/15091))
- **Change account suspensions to be reversible by default** ([Gargron](https://github.com/tootsuite/mastodon/pull/14726), [ThibG](https://github.com/tootsuite/mastodon/pull/15152), [ThibG](https://github.com/tootsuite/mastodon/pull/15106), [ThibG](https://github.com/tootsuite/mastodon/pull/15100), [ThibG](https://github.com/tootsuite/mastodon/pull/15099), [noellabo](https://github.com/tootsuite/mastodon/pull/14855), [ThibG](https://github.com/tootsuite/mastodon/pull/15380), [Gargron](https://github.com/tootsuite/mastodon/pull/15420), [Gargron](https://github.com/tootsuite/mastodon/pull/15414))
- Suspensions no longer equal deletions
- A suspended account can be unsuspended with minimal consequences for 30 days
- Immediate deletion of data is still available as an explicit option
- Suspended accounts can request an archive of their data through the UI
- Change REST API to return empty data for suspended accounts (14765)
- Change web UI to show empty profile for suspended accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/14766), [Gargron](https://github.com/tootsuite/mastodon/pull/15345))
- Change featured hashtag suggestions to be recently used instead of most used ([abcang](https://github.com/tootsuite/mastodon/pull/14760))
- Change direct toots to appear in the home feed again ([Gargron](https://github.com/tootsuite/mastodon/pull/14711), [ThibG](https://github.com/tootsuite/mastodon/pull/15182), [noellabo](https://github.com/tootsuite/mastodon/pull/14727))
- Return to treating all toots the same instead of trying to retrofit direct visibility into an instant messaging model
- Change email address validation to return more specific errors ([ThibG](https://github.com/tootsuite/mastodon/pull/14565))
- Change HTTP signature requirements to include `Digest` header on `POST` requests ([ThibG](https://github.com/tootsuite/mastodon/pull/15069))
- Change click area of video/audio player buttons to be bigger in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/15049))
- Change order of filters by alphabetic by "keyword or phrase" ([ariasuni](https://github.com/tootsuite/mastodon/pull/15050))
- Change suspension of remote accounts to also undo outgoing follows ([ThibG](https://github.com/tootsuite/mastodon/pull/15188))
- Change string "Home" to "Home and lists" in the filter creation screen ([ariasuni](https://github.com/tootsuite/mastodon/pull/15139))
- Change string "Boost to original audience" to "Boost with original visibility" in web UI ([3n-k1](https://github.com/tootsuite/mastodon/pull/14598))
- Change string "Show more" to "Show newer" and "Show older" on public pages ([ariasuni](https://github.com/tootsuite/mastodon/pull/15052))
- Change order of announcements to be reverse chronological in web UI ([dariusk](https://github.com/tootsuite/mastodon/pull/15065), [dariusk](https://github.com/tootsuite/mastodon/pull/15070))
- Change RTL detection to rely on unicode-bidi paragraph by paragraph in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14573))
- Change visibility icon next to timestamp to be clickable in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/15053), [mayaeh](https://github.com/tootsuite/mastodon/pull/15055))
- Change public thread view to hide "Show thread" link ([ThibG](https://github.com/tootsuite/mastodon/pull/15266))
- Change number format on about page from full to shortened ([Gargron](https://github.com/tootsuite/mastodon/pull/15327))
- Change how scheduled tasks run in multi-process environments ([noellabo](https://github.com/tootsuite/mastodon/pull/15314))
- New dedicated queue `scheduler`
- Runs by default when Sidekiq is executed with no options
- Has to be added manually in a multi-process environment
### Removed
- Remove fade-in animation from modals in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15199))
- Remove auto-redirect to direct messages in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15142))
- Remove obsolete IndexedDB operations from web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14730))
- Remove dependency on unused and unmaintained http_parser.rb gem ([ThibG](https://github.com/tootsuite/mastodon/pull/14574))
### Fixed
- Fix layout on about page when contact account has a long username ([ThibG](https://github.com/tootsuite/mastodon/pull/15357))
- Fix follow limit preventing re-following of a moved account ([Gargron](https://github.com/tootsuite/mastodon/pull/14207), [ThibG](https://github.com/tootsuite/mastodon/pull/15384))
- **Fix deletes not reaching every server that interacted with toot** ([Gargron](https://github.com/tootsuite/mastodon/pull/15200))
- Previously, delete of a toot would be primarily sent to the followers of its author, people mentioned in the toot, and people who reblogged the toot
- Now, additionally, it is ensured that it is sent to people who replied to it, favourited it, and to the person it replies to even if that person is not mentioned
- Fix resolving an account through its non-canonical form (i.e. alternate domain) ([ThibG](https://github.com/tootsuite/mastodon/pull/15187))
- Fix sending redundant ActivityPub events when processing remote account deletion ([ThibG](https://github.com/tootsuite/mastodon/pull/15104))
- Fix Move handler not being triggered when failing to fetch target account ([ThibG](https://github.com/tootsuite/mastodon/pull/15107))
- Fix downloading remote media files when server returns empty filename ([ThibG](https://github.com/tootsuite/mastodon/pull/14867))
- Fix account processing failing because of large collections ([ThibG](https://github.com/tootsuite/mastodon/pull/15027))
- Fix not being able to unfavorite toots one has lost access to ([ThibG](https://github.com/tootsuite/mastodon/pull/15192))
- Fix not being able to unbookmark toots one has lost access to ([ThibG](https://github.com/tootsuite/mastodon/pull/14604))
- Fix possible casing inconsistencies in hashtag search ([ThibG](https://github.com/tootsuite/mastodon/pull/14906))
- Fix updating account counters when association is not yet created ([Gargron](https://github.com/tootsuite/mastodon/pull/15108))
- Fix cookies not having a SameSite attribute ([Gargron](https://github.com/tootsuite/mastodon/pull/15098))
- Fix poll ending notifications being created for each vote ([ThibG](https://github.com/tootsuite/mastodon/pull/15071))
- Fix multiple boosts of a same toot erroneously appearing in TL ([ThibG](https://github.com/tootsuite/mastodon/pull/14759))
- Fix asset builds not picking up `CDN_HOST` change ([ThibG](https://github.com/tootsuite/mastodon/pull/14381))
- Fix desktop notifications permission prompt in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14985), [Gargron](https://github.com/tootsuite/mastodon/pull/15141), [ThibG](https://github.com/tootsuite/mastodon/pull/13543), [ThibG](https://github.com/tootsuite/mastodon/pull/15176))
- Some time ago, browsers added a requirement that desktop notification prompts could only be displayed in response to a user-generated event (such as a click)
- This means that for some time, users who haven't already given the permission before were not getting a prompt and as such were not receiving desktop notifications
- Fix "Mark media as sensitive" string not supporting pluralizations in other languages in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/15051))
- Fix glitched image uploads when canvas read access is blocked in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15180))
- Fix some account gallery items having empty labels in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15073))
- Fix alt-key hotkeys activating while typing in a text field in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14942))
- Fix wrong seek bar width on media player in web UI ([mfmfuyu](https://github.com/tootsuite/mastodon/pull/15060))
- Fix logging out on mobile in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14901))
- Fix wrong click area for GIFVs in media modal in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/14615))
- Fix unreadable placeholder text color in high contrast theme in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14803))
- Fix scrolling issues when closing some dropdown menus in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14606))
- Fix notification filter bar incorrectly filtering gaps in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14808))
- Fix disabled boost icon being replaced by private boost icon on hover in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14456))
- Fix hashtag detection in compose form being different to server-side in web UI ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/14484), [ThibG](https://github.com/tootsuite/mastodon/pull/14513))
- Fix home last read marker mishandling gaps in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14809))
- Fix unnecessary re-rendering of various components when typing in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15286))
- Fix notifications being unnecessarily re-rendered in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15312))
- Fix column swiping animation logic in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15301))
- Fix inefficiency when fetching hashtag timeline ([noellabo](https://github.com/tootsuite/mastodon/pull/14861), [akihikodaki](https://github.com/tootsuite/mastodon/pull/14662))
- Fix inefficiency when fetching bookmarks ([akihikodaki](https://github.com/tootsuite/mastodon/pull/14674))
- Fix inefficiency when fetching favourites ([akihikodaki](https://github.com/tootsuite/mastodon/pull/14673))
- Fix inefficiency when fetching media-only account timeline ([akihikodaki](https://github.com/tootsuite/mastodon/pull/14675))
- Fix inefficieny when deleting accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/15387), [ThibG](https://github.com/tootsuite/mastodon/pull/15409), [ThibG](https://github.com/tootsuite/mastodon/pull/15407), [ThibG](https://github.com/tootsuite/mastodon/pull/15408), [ThibG](https://github.com/tootsuite/mastodon/pull/15402), [ThibG](https://github.com/tootsuite/mastodon/pull/15416), [Gargron](https://github.com/tootsuite/mastodon/pull/15421))
- Fix redundant query when processing batch actions on custom emojis ([niwatori24](https://github.com/tootsuite/mastodon/pull/14534))
- Fix slow distinct queries where grouped queries are faster ([Gargron](https://github.com/tootsuite/mastodon/pull/15287))
- Fix performance on instances list in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15282))
- Fix server actor appearing in list of accounts in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14567))
- Fix "bootstrap timeline accounts" toggle in site settings in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15325))
- Fix PostgreSQL secret name for cronjob in Helm chart ([metal3d](https://github.com/tootsuite/mastodon/pull/15072))
- Fix Procfile not being compatible with herokuish ([acuteaura](https://github.com/tootsuite/mastodon/pull/12685))
- Fix installation of tini being split into multiple steps in Dockerfile ([ryncsn](https://github.com/tootsuite/mastodon/pull/14686))
### Security
- Fix streaming API allowing connections to persist after access token invalidation ([Gargron](https://github.com/tootsuite/mastodon/pull/15111))
- Fix 2FA/sign-in token sessions being valid after password change ([Gargron](https://github.com/tootsuite/mastodon/pull/14802))
- Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ThibG](https://github.com/tootsuite/mastodon/pull/15364))
## [3.2.2] - 2020-12-19
### Added
- Add `tootctl maintenance fix-duplicates` ([ThibG](https://github.com/tootsuite/mastodon/pull/14860), [Gargron](https://github.com/tootsuite/mastodon/pull/15223))
- Index corruption in the database?
- This command is for you
### Removed
- Remove dependency on unused and unmaintained http_parser.rb gem ([ThibG](https://github.com/tootsuite/mastodon/pull/14574))
### Fixed
- Fix Move handler not being triggered when failing to fetch target account ([ThibG](https://github.com/tootsuite/mastodon/pull/15107))
- Fix downloading remote media files when server returns empty filename ([ThibG](https://github.com/tootsuite/mastodon/pull/14867))
- Fix possible casing inconsistencies in hashtag search ([ThibG](https://github.com/tootsuite/mastodon/pull/14906))
- Fix updating account counters when association is not yet created ([Gargron](https://github.com/tootsuite/mastodon/pull/15108))
- Fix account processing failing because of large collections ([ThibG](https://github.com/tootsuite/mastodon/pull/15027))
- Fix resolving an account through its non-canonical form (i.e. alternate domain) ([ThibG](https://github.com/tootsuite/mastodon/pull/15187))
- Fix slow distinct queries where grouped queries are faster ([Gargron](https://github.com/tootsuite/mastodon/pull/15287))
### Security
- Fix 2FA/sign-in token sessions being valid after password change ([Gargron](https://github.com/tootsuite/mastodon/pull/14802))
- Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ThibG](https://github.com/tootsuite/mastodon/pull/15364))
## [3.2.1] - 2020-10-19
### Added
@@ -387,14 +185,14 @@ All notable changes to this project will be documented in this file.
- Only then proceed to start removing their data (slow)
- Clear out media attachments in a separate worker (slow)
## [3.1.5] - 2020-07-07
## [v3.1.5] - 2020-07-07
### Security
- Fix media attachment enumeration ([ThibG](https://github.com/tootsuite/mastodon/pull/14254))
- Change rate limits for various paths ([Gargron](https://github.com/tootsuite/mastodon/pull/14253))
- Fix other sessions not being logged out on password change ([Gargron](https://github.com/tootsuite/mastodon/pull/14252))
## [3.1.4] - 2020-05-14
## [v3.1.4] - 2020-05-14
### Added
- Add `vi` to available locales ([taicv](https://github.com/tootsuite/mastodon/pull/13542))
@@ -461,7 +259,7 @@ All notable changes to this project will be documented in this file.
- For apps that self-register on behalf of every individual user (such as most mobile apps), this is a non-issue
- The issue only affects developers of apps who are shared between multiple users, such as server-side apps like cross-posters
## [3.1.3] - 2020-04-05
## [v3.1.3] - 2020-04-05
### Added
- Add ability to filter audit log in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13381))

View File

@@ -4,7 +4,7 @@ FROM ubuntu:20.04 as build-dep
SHELL ["bash", "-c"]
# Install Node v12 (LTS)
ENV NODE_VER="12.20.0"
ENV NODE_VER="12.16.3"
RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \
@@ -36,11 +36,10 @@ RUN apt update && \
./autogen.sh && \
./configure --prefix=/opt/jemalloc && \
make -j$(nproc) > /dev/null && \
make install_bin install_include install_lib && \
cd .. && rm -rf jemalloc-$JE_VER $JE_VER.tar.gz
make install_bin install_include install_lib
# Install Ruby
ENV RUBY_VER="2.7.2"
ENV RUBY_VER="2.6.6"
ENV CPPFLAGS="-I/opt/jemalloc/include"
ENV LDFLAGS="-L/opt/jemalloc/lib/"
RUN apt update && \
@@ -57,8 +56,7 @@ RUN apt update && \
--disable-install-doc && \
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
make -j$(nproc) > /dev/null && \
make install && \
cd .. && rm -rf ruby-$RUBY_VER.tar.gz ruby-$RUBY_VER
make install
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
@@ -109,14 +107,11 @@ RUN apt -y --no-install-recommends install \
rm -rf /var/lib/apt/lists/*
# Add tini
ENV TINI_VERSION="0.19.0"
RUN dpkgArch="$(dpkg --print-architecture)" && \
ARCH=$dpkgArch && \
wget https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH \
https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH.sha256sum && \
cat tini-$ARCH.sha256sum | sha256sum -c - && \
mv tini-$ARCH /tini && rm tini-$ARCH.sha256sum && \
chmod +x /tini
ENV TINI_VERSION="0.18.0"
ENV TINI_SUM="12d20136605531b09a2c2dac02ccee85e1b874eb322ef6baf7561cd93f93c855"
ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tini
RUN echo "$TINI_SUM tini" | sha256sum -c -
RUN chmod +x /tini
# Copy over mastodon source, and dependencies from building, and set permissions
COPY --chown=mastodon:mastodon . /opt/mastodon

62
Gemfile
View File

@@ -5,19 +5,22 @@ ruby '>= 2.5.0', '< 3.0.0'
gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 5.0'
gem 'rails', '~> 5.2.4.4'
gem 'puma', '~> 4.3'
gem 'rails', '~> 5.2.4.3'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.0'
gem 'thor', '~> 0.20'
gem 'rack', '~> 2.2.3'
gem 'thwait', '~> 0.1.0'
gem 'e2mmap', '~> 0.1.0'
gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.2'
gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.7'
gem 'pghero', '~> 2.5'
gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.85', require: false
gem 'aws-sdk-s3', '~> 1.73', require: false
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0'
@@ -27,7 +30,7 @@ gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.7'
gem 'bootsnap', '~> 1.5', require: false
gem 'bootsnap', '~> 1.4', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639'
@@ -41,10 +44,9 @@ group :pam_authentication, optional: true do
end
gem 'net-ldap', '~> 0.16'
gem 'omniauth-cas', '~> 2.0'
gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.9'
gem 'omniauth-rails_csrf_protection', '~> 0.1'
gem 'color_diff', '~> 0.1'
gem 'discard', '~> 1.2'
@@ -53,11 +55,12 @@ gem 'ed25519', '~> 1.2'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.8'
gem 'redis-namespace', '~> 1.7'
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 4.4'
gem 'http_accept_language', '~> 2.1'
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
gem 'httplog', '~> 1.4.3'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2'
@@ -69,8 +72,8 @@ gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.10'
gem 'ox', '~> 2.13'
gem 'parslet'
gem 'parallel', '~> 1.20'
gem 'posix-spawn'
gem 'parallel', '~> 1.19'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
gem 'pundit', '~> 2.1'
gem 'premailer-rails'
gem 'rack-attack', '~> 6.3'
@@ -82,22 +85,20 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 1.1'
gem 'ruby-progressbar', '~> 1.10'
gem 'sanitize', '~> 5.2'
gem 'scenic', '~> 1.5'
gem 'sidekiq', '~> 6.1'
gem 'sidekiq', '~> 6.0'
gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0'
gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.1'
gem 'simple_form', '~> 5.0'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.2.1'
gem 'strong_migrations', '~> 0.7'
gem 'tty-prompt', '~> 0.22', require: false
gem 'stoplight', '~> 2.2.0'
gem 'strong_migrations', '~> 0.6'
gem 'tty-prompt', '~> 0.21', require: false
gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2020'
gem 'webpacker', '~> 5.2'
gem 'webpacker', '~> 5.1'
gem 'webpush'
gem 'webauthn', '~> 3.0.0.alpha1'
gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.1'
@@ -119,33 +120,33 @@ end
group :test do
gem 'capybara', '~> 3.33'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.14'
gem 'faker', '~> 2.13'
gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.19', require: false
gem 'webmock', '~> 3.10'
gem 'parallel_tests', '~> 3.4'
gem 'simplecov', '~> 0.18', require: false
gem 'webmock', '~> 3.8'
gem 'parallel_tests', '~> 3.0'
gem 'rspec_junit_formatter', '~> 0.4'
end
group :development do
gem 'active_record_query_trace', '~> 1.8'
gem 'active_record_query_trace', '~> 1.7'
gem 'annotate', '~> 3.1'
gem 'better_errors', '~> 2.9'
gem 'better_errors', '~> 2.7'
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.1'
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler'
gem 'rubocop', '~> 1.3', require: false
gem 'rubocop-rails', '~> 2.8', require: false
gem 'brakeman', '~> 4.10', require: false
gem 'rubocop', '~> 0.86', require: false
gem 'rubocop-rails', '~> 2.6', require: false
gem 'brakeman', '~> 4.8', require: false
gem 'bundler-audit', '~> 0.7', require: false
gem 'capistrano', '~> 3.14'
gem 'capistrano-rails', '~> 1.6'
gem 'capistrano-rbenv', '~> 2.2'
gem 'capistrano-rails', '~> 1.5'
gem 'capistrano-rbenv', '~> 2.1'
gem 'capistrano-yarn', '~> 2.0'
gem 'stackprof'
@@ -158,6 +159,3 @@ end
gem 'concurrent-ruby', require: false
gem 'connection_pool', require: false
gem 'xorcist', '~> 1.1'
gem 'pluck_each', '~> 0.1.3'

View File

@@ -6,6 +6,21 @@ GIT
health_check (4.0.0.pre)
rails (>= 4.0)
GIT
remote: https://github.com/rtomayko/posix-spawn
revision: 58465d2e213991f8afb13b984854a49fcdcc980c
ref: 58465d2e213991f8afb13b984854a49fcdcc980c
specs:
posix-spawn (0.3.13)
GIT
remote: https://github.com/tmm1/http_parser.rb
revision: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
ref: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
submodules: true
specs:
http_parser.rb (0.6.1)
GIT
remote: https://github.com/witgo/nilsimsa
revision: fd184883048b922b176939f851338d0a4971a532
@@ -16,25 +31,25 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (5.2.4.4)
actionpack (= 5.2.4.4)
actioncable (5.2.4.3)
actionpack (= 5.2.4.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailer (5.2.4.4)
actionpack (= 5.2.4.4)
actionview (= 5.2.4.4)
activejob (= 5.2.4.4)
actionmailer (5.2.4.3)
actionpack (= 5.2.4.3)
actionview (= 5.2.4.3)
activejob (= 5.2.4.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.2.4.4)
actionview (= 5.2.4.4)
activesupport (= 5.2.4.4)
actionpack (5.2.4.3)
actionview (= 5.2.4.3)
activesupport (= 5.2.4.3)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.4.4)
activesupport (= 5.2.4.4)
actionview (5.2.4.3)
activesupport (= 5.2.4.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -44,21 +59,21 @@ GEM
activemodel (>= 4.1, < 6.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.8)
activejob (5.2.4.4)
activesupport (= 5.2.4.4)
active_record_query_trace (1.7)
activejob (5.2.4.3)
activesupport (= 5.2.4.3)
globalid (>= 0.3.6)
activemodel (5.2.4.4)
activesupport (= 5.2.4.4)
activerecord (5.2.4.4)
activemodel (= 5.2.4.4)
activesupport (= 5.2.4.4)
activemodel (5.2.4.3)
activesupport (= 5.2.4.3)
activerecord (5.2.4.3)
activemodel (= 5.2.4.3)
activesupport (= 5.2.4.3)
arel (>= 9.0)
activestorage (5.2.4.4)
actionpack (= 5.2.4.4)
activerecord (= 5.2.4.4)
activestorage (5.2.4.3)
actionpack (= 5.2.4.3)
activerecord (= 5.2.4.3)
marcel (~> 0.3.1)
activesupport (5.2.4.4)
activesupport (5.2.4.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -67,7 +82,6 @@ GEM
public_suffix (>= 2.0.2, < 5.0)
airbrussh (1.4.0)
sshkit (>= 1.6.1, != 1.7.0)
android_key_attestation (0.3.0)
annotate (3.1.1)
activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 14.0)
@@ -77,36 +91,34 @@ GEM
encryptor (~> 3.0.0)
av (0.9.0)
cocaine (~> 0.5.3)
awrence (1.1.1)
aws-eventstream (1.1.0)
aws-partitions (1.397.0)
aws-sdk-core (3.109.3)
aws-partitions (1.338.0)
aws-sdk-core (3.103.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.39.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (1.36.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.85.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-s3 (1.73.0)
aws-sdk-core (~> 3, >= 3.102.1)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-sigv4 (1.2.1)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.16)
better_errors (2.9.1)
bcrypt (3.1.13)
better_errors (2.7.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
bindata (2.4.8)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
blurhash (0.1.4)
ffi (~> 1.10.0)
bootsnap (1.5.1)
bootsnap (1.4.6)
msgpack (~> 1.0)
brakeman (4.10.0)
brakeman (4.8.2)
browser (4.2.0)
builder (3.2.4)
bullet (6.1.0)
@@ -121,12 +133,12 @@ GEM
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
capistrano-bundler (2.0.1)
capistrano-bundler (1.6.0)
capistrano (~> 3.1)
capistrano-rails (1.6.1)
capistrano-rails (1.5.0)
capistrano (~> 3.1)
capistrano-bundler (>= 1.1, < 3)
capistrano-rbenv (2.2.0)
capistrano-bundler (~> 1.1)
capistrano-rbenv (2.1.6)
capistrano (~> 3.1)
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
@@ -141,13 +153,12 @@ GEM
xpath (~> 3.2)
case_transform (0.2)
activesupport
cbor (0.5.9.6)
charlock_holmes (0.7.7)
chewy (5.1.0)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
chunky_png (1.3.12)
chunky_png (1.3.11)
cld3 (3.3.0)
ffi (>= 1.1.0, < 1.12.0)
climate_control (0.2.0)
@@ -155,17 +166,15 @@ GEM
climate_control (>= 0.0.3, < 1.0)
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.1.7)
concurrent-ruby (1.1.6)
connection_pool (2.2.3)
cose (1.0.0)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 0.4.0)
crack (0.4.4)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.6)
css_parser (1.7.1)
addressable
debug_inspector (0.0.3)
devise (4.7.3)
devise (4.7.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@@ -188,33 +197,34 @@ GEM
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.4.0)
railties (>= 5)
dotenv (2.7.6)
dotenv-rails (2.7.6)
dotenv (= 2.7.6)
railties (>= 3.2)
dotenv (2.7.5)
dotenv-rails (2.7.5)
dotenv (= 2.7.5)
railties (>= 3.2, < 6.1)
e2mmap (0.1.0)
ed25519 (1.2.4)
elasticsearch (7.9.0)
elasticsearch-api (= 7.9.0)
elasticsearch-transport (= 7.9.0)
elasticsearch-api (7.9.0)
elasticsearch (7.8.0)
elasticsearch-api (= 7.8.0)
elasticsearch-transport (= 7.8.0)
elasticsearch-api (7.8.0)
multi_json
elasticsearch-dsl (0.1.9)
elasticsearch-transport (7.9.0)
elasticsearch-transport (7.8.0)
faraday (~> 1)
multi_json
encryptor (3.0.0)
equatable (0.6.1)
erubi (1.9.0)
et-orbi (1.2.4)
tzinfo
excon (0.76.0)
excon (0.75.0)
fabrication (2.21.1)
faker (2.14.0)
faker (2.13.0)
i18n (>= 1.6, < 2)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
fast_blank (1.0.0)
fastimage (2.2.0)
fastimage (2.1.7)
ffi (1.10.0)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
@@ -232,7 +242,7 @@ GEM
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.2.5)
fugit (1.3.9)
fugit (1.3.6)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.3)
fuubar (2.5.0)
@@ -240,7 +250,7 @@ GEM
ruby-progressbar (~> 1.4)
globalid (0.4.2)
activesupport (>= 4.2.0)
hamlit (2.13.0)
hamlit (2.11.0)
temple (>= 0.8.2)
thor
tilt
@@ -271,7 +281,7 @@ GEM
httplog (1.4.3)
rack (>= 1.0)
rainbow (>= 2.0.0)
i18n (1.8.5)
i18n (1.8.3)
concurrent-ruby (~> 1.0)
i18n-tasks (0.9.31)
activesupport (>= 4.0.2)
@@ -289,7 +299,7 @@ GEM
jmespath (1.4.0)
json (2.3.1)
json-canonicalization (0.2.0)
json-ld (3.1.5)
json-ld (3.1.4)
htmlentities (~> 4.3)
json-canonicalization (~> 0.2)
link_header (~> 0.0, >= 0.0.8)
@@ -300,7 +310,7 @@ GEM
json-ld (~> 3.1)
rdf (~> 3.1)
jsonapi-renderer (0.2.2)
jwt (2.2.2)
jwt (2.2.1)
kaminari (1.2.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.1)
@@ -327,7 +337,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.7.0)
loofah (2.6.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@@ -340,7 +350,7 @@ GEM
redis (>= 3.0.5)
memory_profiler (0.9.14)
method_source (1.0.0)
microformats (4.2.1)
microformats (4.2.0)
json (~> 2.2)
nokogiri (~> 1.10)
mime-types (3.3.1)
@@ -349,16 +359,17 @@ GEM
mimemagic (0.3.5)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.2)
minitest (5.14.1)
msgpack (1.3.3)
multi_json (1.15.0)
multi_json (1.14.1)
multipart-post (2.1.1)
net-ldap (0.16.3)
necromancer (0.5.1)
net-ldap (0.16.2)
net-scp (3.0.0)
net-ssh (>= 2.6.5, < 7.0.0)
net-ssh (6.1.0)
nio4r (2.5.4)
nokogiri (1.10.10)
nio4r (2.5.2)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.2)
nokogiri (~> 1.8, >= 1.8.4)
@@ -367,24 +378,19 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.10.16)
oj (3.10.6)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
omniauth-cas (2.0.0)
omniauth-cas (1.1.1)
addressable (~> 2.3)
nokogiri (~> 1.5)
omniauth (~> 1.2)
omniauth-rails_csrf_protection (0.1.2)
actionpack (>= 4.2)
omniauth (>= 1.3.1)
omniauth-saml (1.10.3)
omniauth-saml (1.10.2)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.9)
openssl (2.2.0)
openssl-signature_algorithm (0.4.0)
orm_adapter (0.5.0)
ox (2.13.4)
ox (2.13.2)
paperclip (6.0.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
@@ -394,23 +400,20 @@ GEM
paperclip-av-transcoder (0.6.4)
av (~> 0.9.0)
paperclip (>= 2.5.2)
parallel (1.20.1)
parallel_tests (3.4.0)
parallel (1.19.2)
parallel_tests (3.0.0)
parallel
parser (2.7.2.0)
parser (2.7.1.4)
ast (~> 2.4.1)
parslet (2.0.0)
pastel (0.8.0)
pastel (0.7.4)
equatable (~> 0.6)
tty-color (~> 0.5)
pg (1.2.3)
pghero (2.7.2)
pghero (2.5.1)
activerecord (>= 5)
pkg-config (1.4.4)
pluck_each (0.1.3)
activerecord (> 3.2.0)
activesupport (> 3.0.0)
posix-spawn (0.3.15)
premailer (1.14.2)
pkg-config (1.4.1)
premailer (1.11.1)
addressable
css_parser (>= 1.6.0)
htmlentities (>= 4.0.0)
@@ -426,12 +429,12 @@ GEM
pry (~> 0.13.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
puma (5.0.4)
public_suffix (4.0.5)
puma (4.3.5)
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)
raabro (1.3.3)
raabro (1.3.1)
rack (2.2.3)
rack-attack (6.3.1)
rack (>= 1.0, < 3)
@@ -441,18 +444,18 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.4.4)
actioncable (= 5.2.4.4)
actionmailer (= 5.2.4.4)
actionpack (= 5.2.4.4)
actionview (= 5.2.4.4)
activejob (= 5.2.4.4)
activemodel (= 5.2.4.4)
activerecord (= 5.2.4.4)
activestorage (= 5.2.4.4)
activesupport (= 5.2.4.4)
rails (5.2.4.3)
actioncable (= 5.2.4.3)
actionmailer (= 5.2.4.3)
actionpack (= 5.2.4.3)
actionview (= 5.2.4.3)
activejob (= 5.2.4.3)
activemodel (= 5.2.4.3)
activerecord (= 5.2.4.3)
activestorage (= 5.2.4.3)
activesupport (= 5.2.4.3)
bundler (>= 1.3.0)
railties (= 5.2.4.4)
railties (= 5.2.4.3)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@@ -468,20 +471,20 @@ GEM
railties (>= 5.0, < 6)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
railties (5.2.4.4)
actionpack (= 5.2.4.4)
activesupport (= 5.2.4.4)
railties (5.2.4.3)
actionpack (= 5.2.4.3)
activesupport (= 5.2.4.3)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
rainbow (3.0.0)
rake (13.0.1)
rdf (3.1.7)
rdf (3.1.4)
hamster (~> 3.0)
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.4.0)
rdf (~> 3.1)
redis (4.2.5)
redis (4.2.1)
redis-actionpack (5.2.0)
actionpack (>= 5, < 7)
redis-rack (>= 2.1.0, < 3)
@@ -489,9 +492,9 @@ GEM
redis-activesupport (5.2.0)
activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2)
redis-namespace (1.8.0)
redis-namespace (1.7.0)
redis (>= 3.0.4)
redis-rack (2.1.3)
redis-rack (2.1.2)
rack (>= 2.0.8, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
@@ -500,7 +503,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.9.0)
redis (>= 4, < 5)
regexp_parser (1.8.2)
regexp_parser (1.7.1)
request_store (1.5.0)
rack (>= 1.4)
responders (3.0.1)
@@ -513,7 +516,7 @@ GEM
chunky_png (~> 1.0)
rqrcode_core (~> 0.1)
rqrcode_core (0.1.2)
rspec-core (3.9.3)
rspec-core (3.9.2)
rspec-support (~> 3.9.3)
rspec-expectations (3.9.2)
diff-lcs (>= 1.2.0, < 2.0)
@@ -535,38 +538,33 @@ GEM
rspec-support (3.9.3)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.3.1)
rubocop (0.86.0)
parallel (~> 1.10)
parser (>= 2.7.1.5)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8)
regexp_parser (>= 1.7)
rexml
rubocop-ast (>= 1.1.1)
rubocop-ast (>= 0.0.3, < 1.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (1.1.1)
parser (>= 2.7.1.5)
rubocop-rails (2.8.1)
rubocop-ast (0.1.0)
parser (>= 2.7.0.1)
rubocop-rails (2.6.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 0.87.0)
rubocop (>= 0.82.0)
ruby-progressbar (1.10.1)
ruby-saml (1.11.0)
nokogiri (>= 1.5.10)
rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
safe_yaml (1.0.5)
sanitize (5.2.1)
crass (~> 1.0.2)
nokogiri (>= 1.8.0)
nokogumbo (~> 2.0)
scenic (1.5.4)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
securecompare (1.0.0)
semantic_range (2.3.0)
sidekiq (6.1.2)
sidekiq (6.1.0)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
@@ -579,87 +577,74 @@ GEM
sidekiq (>= 3)
thwait
tilt (>= 1.4.0)
sidekiq-unique-jobs (6.0.25)
sidekiq-unique-jobs (6.0.22)
concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 4.0, < 7.0)
thor (>= 0.20, < 2.0)
thor (~> 0)
simple-navigation (4.1.0)
activesupport (>= 2.3.2)
simple_form (5.0.3)
simple_form (5.0.2)
actionpack (>= 5.0)
activemodel (>= 5.0)
simplecov (0.19.1)
simplecov (0.18.5)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov-html (0.12.3)
simplecov-html (0.12.2)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.2)
sprockets-rails (3.2.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sshkit (1.21.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
stackprof (0.2.16)
stackprof (0.2.15)
statsd-ruby (1.4.0)
stoplight (2.2.1)
stoplight (2.2.0)
streamio-ffmpeg (3.0.2)
multi_json (~> 1.8)
strong_migrations (0.7.2)
strong_migrations (0.6.8)
activerecord (>= 5)
temple (0.8.2)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
thor (1.0.1)
thor (0.20.3)
thread_safe (0.3.6)
thwait (0.2.0)
e2mmap
thwait (0.1.0)
tilt (2.0.10)
tpm-key_attestation (0.9.0)
bindata (~> 2.4)
openssl-signature_algorithm (~> 0.4.0)
tty-color (0.5.2)
tty-color (0.5.1)
tty-cursor (0.7.1)
tty-prompt (0.22.0)
pastel (~> 0.8)
tty-reader (~> 0.8)
tty-reader (0.8.0)
tty-prompt (0.21.0)
necromancer (~> 0.5.0)
pastel (~> 0.7.0)
tty-reader (~> 0.7.0)
tty-reader (0.7.0)
tty-cursor (~> 0.7)
tty-screen (~> 0.8)
wisper (~> 2.0)
tty-screen (0.8.1)
tty-screen (~> 0.7)
wisper (~> 2.0.0)
tty-screen (0.8.0)
twitter-text (1.14.7)
unf (~> 0.1.0)
tzinfo (1.2.7)
thread_safe (~> 0.1)
tzinfo-data (1.2020.4)
tzinfo-data (1.2020.1)
tzinfo (>= 1.0.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
uniform_notifier (1.13.0)
warden (1.2.9)
rack (>= 2.0.9)
webauthn (3.0.0.alpha1)
android_key_attestation (~> 0.3.0)
awrence (~> 1.1)
bindata (~> 2.4)
cbor (~> 0.5.9)
cose (~> 1.0)
openssl (~> 2.0)
safety_net_attestation (~> 0.4.0)
securecompare (~> 1.0)
tpm-key_attestation (~> 0.9.0)
webmock (3.10.0)
warden (1.2.8)
rack (>= 2.0.6)
webmock (3.8.3)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpacker (5.2.1)
webpacker (5.1.1)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
@@ -667,11 +652,10 @@ GEM
webpush (0.3.8)
hkdf (~> 0.2)
jwt (~> 2.0)
websocket-driver (0.7.3)
websocket-driver (0.7.2)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
wisper (2.0.1)
xorcist (1.1.2)
xpath (3.2.0)
nokogiri (~> 1.8)
@@ -680,21 +664,21 @@ PLATFORMS
DEPENDENCIES
active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.8)
active_record_query_trace (~> 1.7)
addressable (~> 2.7)
annotate (~> 3.1)
aws-sdk-s3 (~> 1.85)
better_errors (~> 2.9)
aws-sdk-s3 (~> 1.73)
better_errors (~> 2.7)
binding_of_caller (~> 0.7)
blurhash (~> 0.1)
bootsnap (~> 1.5)
brakeman (~> 4.10)
bootsnap (~> 1.4)
brakeman (~> 4.8)
browser
bullet (~> 6.1)
bundler-audit (~> 0.7)
capistrano (~> 3.14)
capistrano-rails (~> 1.6)
capistrano-rbenv (~> 2.2)
capistrano-rails (~> 1.5)
capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0)
capybara (~> 3.33)
charlock_holmes (~> 0.7.7)
@@ -710,9 +694,10 @@ DEPENDENCIES
discard (~> 1.2)
doorkeeper (~> 5.4)
dotenv-rails (~> 2.7)
e2mmap (~> 0.1.0)
ed25519 (~> 1.2)
fabrication (~> 2.21)
faker (~> 2.14)
faker (~> 2.13)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.1.0)
@@ -724,6 +709,7 @@ DEPENDENCIES
htmlentities (~> 4.3)
http (~> 4.4)
http_accept_language (~> 2.1)
http_parser.rb (~> 0.6)!
httplog (~> 1.4.3)
i18n-tasks (~> 0.9)
idn-ruby
@@ -746,65 +732,61 @@ DEPENDENCIES
nsa (~> 0.2)
oj (~> 3.10)
omniauth (~> 1.9)
omniauth-cas (~> 2.0)
omniauth-rails_csrf_protection (~> 0.1)
omniauth-cas (~> 1.1)
omniauth-saml (~> 1.10)
ox (~> 2.13)
paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6)
parallel (~> 1.20)
parallel_tests (~> 3.4)
parallel (~> 1.19)
parallel_tests (~> 3.0)
parslet
pg (~> 1.2)
pghero (~> 2.7)
pghero (~> 2.5)
pkg-config (~> 1.4)
pluck_each (~> 0.1.3)
posix-spawn
posix-spawn!
premailer-rails
private_address_check (~> 0.5)
pry-byebug (~> 3.9)
pry-rails (~> 0.3)
puma (~> 5.0)
puma (~> 4.3)
pundit (~> 2.1)
rack (~> 2.2.3)
rack-attack (~> 6.3)
rack-cors (~> 1.1)
rails (~> 5.2.4.4)
rails (~> 5.2.4.3)
rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6)
rdf-normalize (~> 0.4)
redis (~> 4.2)
redis-namespace (~> 1.8)
redis-namespace (~> 1.7)
redis-rails (~> 5.0)
rqrcode (~> 1.1)
rspec-rails (~> 4.0)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.4)
rubocop (~> 1.3)
rubocop-rails (~> 2.8)
rubocop (~> 0.86)
rubocop-rails (~> 2.6)
ruby-progressbar (~> 1.10)
sanitize (~> 5.2)
scenic (~> 1.5)
sidekiq (~> 6.1)
sidekiq (~> 6.0)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0)
sidekiq-unique-jobs (~> 6.0)
simple-navigation (~> 4.1)
simple_form (~> 5.0)
simplecov (~> 0.19)
simplecov (~> 0.18)
sprockets (~> 3.7.2)
sprockets-rails (~> 3.2)
stackprof
stoplight (~> 2.2.1)
stoplight (~> 2.2.0)
streamio-ffmpeg (~> 3.0)
strong_migrations (~> 0.7)
thor (~> 1.0)
tty-prompt (~> 0.22)
strong_migrations (~> 0.6)
thor (~> 0.20)
thwait (~> 0.1.0)
tty-prompt (~> 0.21)
twitter-text (~> 1.14)
tzinfo-data (~> 1.2020)
webauthn (~> 3.0.0.alpha1)
webmock (~> 3.10)
webpacker (~> 5.2)
webmock (~> 3.8)
webpacker (~> 5.1)
webpush
xorcist (~> 1.1)

View File

@@ -1,4 +1,4 @@
web: bin/heroku-web
web: if [ "$RUN_STREAMING" != "true" ]; then BIND=0.0.0.0 bundle exec puma -C config/puma.rb; else BIND=0.0.0.0 node ./streaming; fi
worker: bundle exec sidekiq
# For the streaming API, you need a separate app that shares Postgres and Redis:

View File

@@ -1,15 +1,12 @@
# frozen_string_literal: true
class AboutController < ApplicationController
include RegistrationSpamConcern
layout 'public'
before_action :require_open_federation!, only: [:show, :more]
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
before_action :set_expires_in, only: [:more, :terms]
before_action :set_registration_form_time, only: :show
before_action :set_expires_in, only: [:show, :more, :terms]
skip_before_action :require_functional!, only: [:more, :terms]

View File

@@ -29,7 +29,8 @@ class AccountsController < ApplicationController
end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = cached_filtered_status_page
@statuses = filtered_status_page
@statuses = cache_collection(@statuses, Status)
@rss_url = rss_url
unless @statuses.empty?
@@ -81,7 +82,7 @@ class AccountsController < ApplicationController
end
def account_media_status_ids
@account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
end
def no_replies_scope
@@ -102,10 +103,6 @@ class AccountsController < ApplicationController
params[:username]
end
def skip_temporary_suspension_response?
request.format == :json
end
def rss_url
if tag_requested?
short_account_tag_url(@account, params[:tag], format: 'rss')
@@ -146,13 +143,8 @@ class AccountsController < ApplicationController
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end
def cached_filtered_status_page
cache_collection_paginated_by_id(
filtered_statuses,
Status,
PAGE_SIZE,
params_slice(:max_id, :min_id, :since_id)
)
def filtered_status_page
filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id))
end
def params_slice(*keys)

View File

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

View File

@@ -12,7 +12,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def show
expires_in 3.minutes, public: public_fetch_mode?
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
end
private
@@ -20,9 +20,17 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_items
case params[:id]
when 'featured'
@items = for_signed_account { cache_collection(@account.pinned_statuses, Status) }
when 'tags'
@items = for_signed_account { @account.featured_tags }
@items = begin
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
[]
else
cache_collection(@account.pinned_statuses, Status)
end
end
when 'devices'
@items = @account.devices
else
@@ -32,7 +40,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_size
case params[:id]
when 'featured', 'devices', 'tags'
when 'featured', 'devices'
@size = @items.size
else
not_found
@@ -43,7 +51,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
case params[:id]
when 'featured'
@type = :ordered
when 'devices', 'tags'
when 'devices'
@type = :unordered
else
not_found
@@ -58,16 +66,4 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
items: @items
)
end
def for_signed_account
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
[]
else
yield
end
end
end

View File

@@ -1,36 +0,0 @@
# frozen_string_literal: true
class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
before_action :require_signature!
before_action :set_items
before_action :set_cache_headers
def show
expires_in 0, public: false
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
private
def uri_prefix
signed_request_account.uri[/http(s?):\/\/[^\/]+\//]
end
def set_items
@items = @account.followers.where(Account.arel_table[:uri].matches(uri_prefix + '%', false, true)).pluck(:uri)
end
def collection_presenter
ActivityPub::CollectionPresenter.new(
id: account_followers_synchronization_url(@account),
type: :ordered,
items: @items
)
end
end

View File

@@ -11,7 +11,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
def create
upgrade_account
process_collection_synchronization
process_payload
head 202
end
@@ -33,10 +32,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
params[:account_username].present?
end
def skip_temporary_suspension_response?
true
end
def body
return @body if defined?(@body)
@@ -57,19 +52,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
end
def process_collection_synchronization
raw_params = request.headers['Collection-Synchronization']
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true'
# Re-using the syntax for signature parameters
tree = SignatureParamsParser.new.parse(raw_params)
params = SignatureParamsTransformer.new.apply(tree)
ActivityPub::PrepareFollowersSynchronizationService.new.call(signed_request_account, params)
rescue Parslet::ParseFailed
Rails.logger.warn 'Error parsing Collection-Synchronization header'
end
def process_payload
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
end

View File

@@ -20,9 +20,9 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def outbox_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
id: outbox_url(page_params),
id: account_outbox_url(@account, page_params),
type: :ordered,
part_of: outbox_url,
part_of: account_outbox_url(@account),
prev: prev_page,
next: next_page,
items: @statuses
@@ -32,20 +32,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
id: account_outbox_url(@account),
type: :ordered,
size: @account.statuses_count,
first: outbox_url(page: true),
last: outbox_url(page: true, min_id: 0)
first: account_outbox_url(@account, page: true),
last: account_outbox_url(@account, page: true, min_id: 0)
)
end
end
def outbox_url(**kwargs)
if params[:account_username].present?
account_outbox_url(@account, **kwargs)
else
instance_actor_outbox_url(**kwargs)
end
end
def next_page
account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
end
@@ -57,12 +49,9 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_statuses
return unless page_requested?
@statuses = cache_collection_paginated_by_id(
@account.statuses.permitted_for(@account, signed_request_account),
Status,
LIMIT,
params_slice(:max_id, :min_id, :since_id)
)
@statuses = @account.statuses.permitted_for(@account, signed_request_account)
@statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id))
@statuses = cache_collection(@statuses, Status)
end
def page_requested?
@@ -72,8 +61,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def page_params
{ page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact
end
def set_account
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
end
end

View File

@@ -31,7 +31,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end
def set_replies
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end

View File

@@ -2,7 +2,7 @@
module Admin
class AccountsController < BaseController
before_action :set_account, except: [:index]
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
before_action :require_remote_account!, only: [:redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
@@ -14,65 +14,49 @@ module Admin
def show
authorize @account, :show?
@deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
@domain_block = DomainBlock.rule_for(@account.domain)
end
def memorialize
authorize @account, :memorialize?
@account.memorialize!
log_action :memorialize, @account
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def approve
authorize @account.user, :approve?
@account.user.approve!
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
redirect_to admin_pending_accounts_path
end
def reject
authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
end
def destroy
authorize @account, :destroy?
Admin::AccountDeletionWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end
def unsensitive
authorize @account, :unsensitive?
@account.unsensitize!
log_action :unsensitive, @account
redirect_to admin_account_path(@account.id)
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
redirect_to admin_pending_accounts_path
end
def unsilence
authorize @account, :unsilence?
@account.unsilence!
log_action :unsilence, @account
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def redownload
@@ -81,7 +65,7 @@ module Admin
@account.update!(last_webfingered_at: nil)
ResolveAccountService.new.call(@account)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def remove_avatar
@@ -92,7 +76,7 @@ module Admin
log_action :remove_avatar, @account.user
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def remove_header
@@ -103,7 +87,7 @@ module Admin
log_action :remove_header, @account.user
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
private

View File

@@ -71,7 +71,7 @@ class Admin::AnnouncementsController < Admin::BaseController
private
def set_announcements
@announcements = AnnouncementFilter.new(filter_params).results.reverse_chronological.page(params[:page])
@announcements = AnnouncementFilter.new(filter_params).results.page(params[:page])
end
def set_announcement

View File

@@ -29,7 +29,6 @@ module Admin
@domain_block = existing_domain_block
@domain_block.update(resource_params)
end
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block
@@ -41,7 +40,7 @@ module Admin
end
def update
authorize :domain_block, :update?
authorize :domain_block, :create?
@domain_block.update(update_params)
@@ -49,7 +48,7 @@ module Admin
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
log_action :update, @domain_block
log_action :create, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else
render :edit
@@ -74,11 +73,11 @@ module Admin
end
def update_params
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment)
end
def resource_params
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment)
end
end
end

View File

@@ -27,7 +27,7 @@ module Admin
ips = []
Resolv::DNS.open do |dns|
dns.timeouts = 5
dns.timeouts = 1
hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }

View File

@@ -2,31 +2,65 @@
module Admin
class InstancesController < BaseController
before_action :set_instances, only: :index
before_action :set_domain_block, only: :show
before_action :set_domain_allow, only: :show
before_action :set_instance, only: :show
def index
authorize :instance, :index?
@instances = ordered_instances
end
def show
authorize :instance, :show?
@following_count = Follow.where(account: Account.where(domain: params[:id])).count
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(params[:id])
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@private_comment = @domain_block&.private_comment
@public_comment = @domain_block&.public_comment
end
private
def set_instance
@instance = Instance.find(params[:id])
def set_domain_block
@domain_block = DomainBlock.rule_for(params[:id])
end
def set_instances
@instances = filtered_instances.page(params[:page])
def set_domain_allow
@domain_allow = DomainAllow.rule_for(params[:id])
end
def set_instance
resource = Account.by_domain_accounts.find_by(domain: params[:id])
resource ||= @domain_block
resource ||= @domain_allow
if resource
@instance = Instance.new(resource)
else
not_found
end
end
def filtered_instances
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
end
def paginated_instances
filtered_instances.page(params[:page])
end
helper_method :paginated_instances
def ordered_instances
paginated_instances.map { |resource| Instance.new(resource) }
end
def filter_params
params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS)
end

View File

@@ -1,56 +0,0 @@
# frozen_string_literal: true
module Admin
class IpBlocksController < BaseController
def index
authorize :ip_block, :index?
@ip_blocks = IpBlock.page(params[:page])
@form = Form::IpBlockBatch.new
end
def new
authorize :ip_block, :create?
@ip_block = IpBlock.new(ip: '', severity: :no_access, expires_in: 1.year)
end
def create
authorize :ip_block, :create?
@ip_block = IpBlock.new(resource_params)
if @ip_block.save
log_action :create, @ip_block
redirect_to admin_ip_blocks_path, notice: I18n.t('admin.ip_blocks.created_msg')
else
render :new
end
end
def batch
@form = Form::IpBlockBatch.new(form_ip_block_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.ip_blocks.no_ip_block_selected')
rescue Mastodon::NotPermittedError
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
ensure
redirect_to admin_ip_blocks_path
end
private
def resource_params
params.require(:ip_block).permit(:ip, :severity, :comment, :expires_in)
end
def action_from_button
'delete' if params[:delete]
end
def form_ip_block_batch_params
params.require(:form_ip_block_batch).permit(ip_block_ids: [])
end
end
end

View File

@@ -14,7 +14,7 @@ module Admin
@statuses = @account.statuses.where(visibility: [:public, :unlisted])
if params[:media]
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
@statuses.merge!(Status.where(id: account_media_status_ids))
end

View File

@@ -40,7 +40,7 @@ class Api::BaseController < ApplicationController
render json: { error: 'This action is not allowed' }, status: 403
end
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do
rescue_from Mastodon::RaceConditionError do
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
end
@@ -71,7 +71,6 @@ class Api::BaseController < ApplicationController
def limit_param(default_limit)
return default_limit unless params[:limit]
[params[:limit].to_i.abs, default_limit * 2].min
end
@@ -96,14 +95,14 @@ class Api::BaseController < ApplicationController
def require_user!
if !current_user
render json: { error: 'This method requires an authenticated user' }, status: 422
elsif current_user.disabled?
render json: { error: 'Your login is currently disabled' }, status: 403
elsif !current_user.confirmed?
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
elsif !current_user.approved?
render json: { error: 'Your login is currently pending approval' }, status: 403
elsif !current_user.functional?
render json: { error: 'Your login is currently disabled' }, status: 403
else
update_user_sign_in
set_user_activity
end
end

View File

@@ -1,22 +0,0 @@
# frozen_string_literal: true
class Api::V1::Accounts::FeaturedTagsController < Api::BaseController
before_action :set_account
before_action :set_featured_tags
respond_to :json
def index
render json: @featured_tags, each_serializer: REST::FeaturedTagSerializer
end
private
def set_account
@account = Account.find(params[:account_id])
end
def set_featured_tags
@featured_tags = @account.suspended? ? [] : @account.featured_tags
end
end

View File

@@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end
def hide_results?
@account.suspended? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
(@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

View File

@@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end
def hide_results?
@account.suspended? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
(@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

View File

@@ -5,7 +5,7 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController
before_action :set_account
def index
@proofs = @account.suspended? ? [] : @account.identity_proofs.active
@proofs = @account.identity_proofs.active
render json: @proofs, each_serializer: REST::IdentityProofSerializer
end

View File

@@ -6,7 +6,7 @@ class Api::V1::Accounts::ListsController < Api::BaseController
before_action :set_account
def index
@lists = @account.suspended? ? [] : @account.lists.where(account: current_account)
@lists = @account.lists.where(account: current_account)
render json: @lists, each_serializer: REST::ListSerializer
end

View File

@@ -5,7 +5,7 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user!
def index
accounts = Account.without_suspended.where(id: account_ids).select('id')
accounts = Account.where(id: account_ids).select('id')
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact

View File

@@ -18,10 +18,14 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
def load_statuses
@account.suspended? ? [] : cached_account_statuses
cached_account_statuses
end
def cached_account_statuses
cache_collection account_statuses, Status
end
def account_statuses
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
@@ -29,12 +33,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
statuses.merge!(hashtag_scope) if params[:tagged].present?
cache_collection_paginated_by_id(
statuses,
Status,
limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def permitted_account_statuses
@@ -42,7 +41,17 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
def only_media_scope
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
Status.where(id: account_media_status_ids)
end
def account_media_status_ids
# `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
# Also, Avoid getting slow by not narrowing down by `statuses.account_id`.
# When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used
# and the table will be joined by `Merge Semi Join`, so the query will be slow.
@account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account)
.paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
.reorder(id: :desc).distinct(:id).pluck(:id)
end
def pinned_scope

View File

@@ -9,6 +9,7 @@ class Api::V1::AccountsController < Api::BaseController
before_action :require_user!, except: [:show, :create]
before_action :set_account, except: [:create]
before_action :check_account_suspension, only: [:show]
before_action :check_enabled_registrations, only: [:create]
skip_before_action :require_authenticated_user!, only: :create
@@ -20,7 +21,7 @@ class Api::V1::AccountsController < Api::BaseController
end
def create
token = AppSignUpService.new.call(doorkeeper_token.application, request.remote_ip, account_params)
token = AppSignUpService.new.call(doorkeeper_token.application, account_params)
response = Doorkeeper::OAuth::TokenResponse.new(token)
headers.merge!(response.headers)
@@ -30,8 +31,9 @@ class Api::V1::AccountsController < Api::BaseController
end
def follow
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end
@@ -42,7 +44,7 @@ class Api::V1::AccountsController < Api::BaseController
end
def mute
MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration] || 0))
MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications))
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
@@ -71,6 +73,10 @@ class Api::V1::AccountsController < Api::BaseController
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options)
end
def check_account_suspension
gone if @account.suspended?
end
def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason)
end

View File

@@ -22,7 +22,6 @@ class Api::V1::Admin::AccountsController < Api::BaseController
active
pending
disabled
sensitized
silenced
suspended
username
@@ -59,20 +58,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def reject
authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
render json: @account, serializer: REST::Admin::AccountSerializer
end
def destroy
authorize @account, :destroy?
Admin::AccountDeletionWorker.perform_async(@account.id)
render json: @account, serializer: REST::Admin::AccountSerializer
end
def unsensitive
authorize @account, :unsensitive?
@account.unsensitize!
log_action :unsensitive, @account
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
render json: @account, serializer: REST::Admin::AccountSerializer
end
@@ -86,7 +72,6 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end
@@ -94,7 +79,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
private
def set_accounts
@accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
@accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def set_account

View File

@@ -63,7 +63,7 @@ class Api::V1::Admin::ReportsController < Api::BaseController
private
def set_reports
@reports = filtered_reports.order(id: :desc).with_accounts.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
@reports = filtered_reports.order(id: :desc).with_accounts.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def set_report

View File

@@ -18,8 +18,6 @@ class Api::V1::BlocksController < Api::BaseController
def paginated_blocks
@paginated_blocks ||= Block.eager_load(target_account: :account_stat)
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),

View File

@@ -17,11 +17,14 @@ class Api::V1::BookmarksController < Api::BaseController
end
def cached_bookmarks
cache_collection(results.map(&:status), Status)
cache_collection(
Status.reorder(nil).joins(:bookmarks).merge(results),
Status
)
end
def results
@_results ||= account_bookmarks.eager_load(:status).to_a_paginated_by_id(
@_results ||= account_bookmarks.paginate_by_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)

View File

@@ -32,7 +32,7 @@ class Api::V1::ConversationsController < Api::BaseController
def paginated_conversations
AccountConversation.where(account: current_account)
.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def insert_pagination_headers

View File

@@ -26,7 +26,7 @@ class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController
end
def set_encrypted_messages
@encrypted_messages = @current_device.encrypted_messages.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
@encrypted_messages = @current_device.encrypted_messages.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def insert_pagination_headers

View File

@@ -25,7 +25,7 @@ class Api::V1::EndorsementsController < Api::BaseController
end
def endorsed_accounts
current_account.endorsed_accounts.includes(:account_stat).without_suspended
current_account.endorsed_accounts.includes(:account_stat)
end
def insert_pagination_headers

View File

@@ -17,11 +17,14 @@ class Api::V1::FavouritesController < Api::BaseController
end
def cached_favourites
cache_collection(results.map(&:status), Status)
cache_collection(
Status.reorder(nil).joins(:favourites).merge(results),
Status
)
end
def results
@_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id(
@_results ||= account_favourites.paginate_by_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)

View File

@@ -3,15 +3,15 @@
class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
before_action :require_user!
before_action :set_recently_used_tags, only: :index
before_action :set_most_used_tags, only: :index
def index
render json: @recently_used_tags, each_serializer: REST::TagSerializer
render json: @most_used_tags, each_serializer: REST::TagSerializer
end
private
def set_recently_used_tags
@recently_used_tags = Tag.recently_used(current_account).where.not(id: current_account.featured_tags).limit(10)
def set_most_used_tags
@most_used_tags = Tag.most_used(current_account).where.not(id: current_account.featured_tags).limit(10)
end
end

View File

@@ -13,7 +13,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
def authorize
AuthorizeFollowService.new.call(account, current_account)
NotifyService.new.call(current_account, :follow, Follow.find_by(account: account, target_account: current_account))
NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account))
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
end
@@ -37,7 +37,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
end
def default_accounts
Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests)
Account.includes(:follow_requests, :account_stat).references(:follow_requests)
end
def paginated_follow_requests

View File

@@ -8,7 +8,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
def index
expires_in 1.day, public: true
render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) }
render_with_cache(expires_in: 1.day) { Account.remote.domains }
end
private

View File

@@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController
def load_accounts
if unlimited?
@list.accounts.without_suspended.includes(:account_stat).all
@list.accounts.includes(:account_stat).all
else
@list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
@list.accounts.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
end
end

View File

@@ -38,6 +38,6 @@ class Api::V1::ListsController < Api::BaseController
end
def list_params
params.permit(:title, :replies_policy)
params.permit(:title)
end
end

View File

@@ -7,7 +7,7 @@ class Api::V1::MutesController < Api::BaseController
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::MutedAccountSerializer
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
@@ -18,8 +18,6 @@ class Api::V1::MutesController < Api::BaseController
def paginated_mutes
@paginated_mutes ||= Mute.eager_load(:target_account)
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),

View File

@@ -14,7 +14,7 @@ class Api::V1::NotificationsController < Api::BaseController
end
def show
@notification = current_account.notifications.without_suspended.find(params[:id])
@notification = current_account.notifications.find(params[:id])
render json: @notification, serializer: REST::NotificationSerializer
end
@@ -31,16 +31,18 @@ class Api::V1::NotificationsController < Api::BaseController
private
def load_notifications
cache_collection_paginated_by_id(
browserable_account_notifications,
Notification,
cache_collection paginated_notifications, Notification
end
def paginated_notifications
browserable_account_notifications.paginate_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
end
def browserable_account_notifications
current_account.notifications.without_suspended.browserable(exclude_types, from_account)
current_account.notifications.browserable(exclude_types, from_account)
end
def target_statuses_from_notifications

View File

@@ -52,6 +52,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
def data_params
return {} if params[:data].blank?
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end
end

View File

@@ -32,7 +32,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
private
def set_statuses
@statuses = current_account.scheduled_statuses.to_a_paginated_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
@statuses = current_account.scheduled_statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def set_status

View File

@@ -5,7 +5,7 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user!
before_action :set_status, only: [:create]
before_action :set_status
def create
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
@@ -13,20 +13,10 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
end
def destroy
bookmark = current_account.bookmarks.find_by(status_id: params[:status_id])
if bookmark
@status = bookmark.status
else
@status = Status.find(params[:status_id])
authorize @status, :show?
end
bookmark = current_account.bookmarks.find_by(status: @status)
bookmark&.destroy!
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
rescue Mastodon::NotPermittedError
not_found
end
private

View File

@@ -22,7 +22,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
def default_accounts
Account
.without_suspended
.includes(:favourites, :account_stat)
.references(:favourites)
.where(favourites: { status_id: @status.id })

View File

@@ -5,7 +5,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user!
before_action :set_status, only: [:create]
before_action :set_status
def create
FavouriteService.new.call(current_account, @status)
@@ -13,19 +13,8 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
end
def destroy
fav = current_account.favourites.find_by(status_id: params[:status_id])
if fav
@status = fav.status
UnfavouriteWorker.perform_async(current_account.id, @status.id)
else
@status = Status.find(params[:status_id])
authorize @status, :show?
end
UnfavouriteWorker.perform_async(current_account.id, @status.id)
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false })
rescue Mastodon::NotPermittedError
not_found
end
private

View File

@@ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
end
def default_accounts
Account.without_suspended.includes(:statuses, :account_stat).references(:statuses)
Account.includes(:statuses, :account_stat).references(:statuses)
end
def paginated_statuses

View File

@@ -16,29 +16,30 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end
def load_statuses
cached_public_statuses_page
cached_public_statuses
end
def cached_public_statuses_page
cache_collection(public_statuses, Status)
def cached_public_statuses
cache_collection public_statuses, Status
end
def public_statuses
public_feed.get(
statuses = public_timeline_statuses.paginate_by_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id],
params[:min_id]
params_slice(:max_id, :since_id, :min_id)
)
if truthy_param?(:only_media)
# `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
statuses.where(id: status_ids)
else
statuses
end
end
def public_feed
PublicFeed.new(
current_account,
local: truthy_param?(:local),
remote: truthy_param?(:remote),
only_media: truthy_param?(:only_media)
)
def public_timeline_statuses
Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local))
end
def insert_pagination_headers

View File

@@ -20,29 +20,30 @@ class Api::V1::Timelines::TagController < Api::BaseController
end
def cached_tagged_statuses
@tag.nil? ? [] : cache_collection(tag_timeline_statuses, Status)
cache_collection tagged_statuses, Status
end
def tagged_statuses
if @tag.nil?
[]
else
statuses = tag_timeline_statuses.paginate_by_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
if truthy_param?(:only_media)
# `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
statuses.where(id: status_ids)
else
statuses
end
end
end
def tag_timeline_statuses
tag_feed.get(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id],
params[:min_id]
)
end
def tag_feed
TagFeed.new(
@tag,
current_account,
any: params[:any],
all: params[:all],
none: params[:none],
local: truthy_param?(:local),
remote: truthy_param?(:remote),
only_media: truthy_param?(:only_media)
)
HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, truthy_param?(:local))
end
def insert_pagination_headers

View File

@@ -22,7 +22,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
reblog: alerts_enabled,
mention: alerts_enabled,
poll: alerts_enabled,
status: alerts_enabled,
},
}
@@ -58,6 +57,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def data_params
@data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
@data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end
end

View File

@@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, with: :service_unavailable
rescue_from Mastodon::RaceConditionError, with: :service_unavailable
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?

View File

@@ -2,7 +2,6 @@
class Auth::RegistrationsController < Devise::RegistrationsController
include Devise::Controllers::Rememberable
include RegistrationSpamConcern
layout :determine_layout
@@ -14,7 +13,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update]
before_action :set_cache_headers, only: [:edit, :update]
before_action :set_registration_form_time, only: :new
skip_before_action :require_functional!, only: [:edit, :update]
@@ -47,17 +45,16 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def build_resource(hash = nil)
super(hash)
resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.registration_form_time = session[:registration_form_time]
resource.sign_up_ip = request.remote_ip
resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.current_sign_in_ip = request.remote_ip
resource.build_account if resource.account.nil?
end
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement)
end
end

View File

@@ -7,7 +7,6 @@ class Auth::SessionsController < Devise::SessionsController
skip_before_action :require_no_authentication, only: [:create]
skip_before_action :require_functional!
skip_before_action :update_user_sign_in
include TwoFactorAuthenticationConcern
include SignInTokenAuthenticationConcern
@@ -25,7 +24,6 @@ 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)
end
@@ -39,27 +37,11 @@ class Auth::SessionsController < Devise::SessionsController
store_location_for(:user, tmp_stored_location) if continue_after?
end
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)
)
session[:webauthn_challenge] = options_for_get.challenge
render json: options_for_get, status: :ok
else
render json: { error: t('webauthn_credentials.not_enabled') }, status: :unauthorized
end
end
protected
def find_user
if session[:attempt_user_id]
User.find_by(id: session[:attempt_user_id])
User.find(session[:attempt_user_id])
else
user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
@@ -69,7 +51,7 @@ class Auth::SessionsController < Devise::SessionsController
end
def user_params
params.require(:user).permit(:email, :password, :otp_attempt, :sign_in_token_attempt, credential: {})
params.require(:user).permit(:email, :password, :otp_attempt, :sign_in_token_attempt)
end
def after_sign_in_path_for(resource)
@@ -92,7 +74,6 @@ class Auth::SessionsController < Devise::SessionsController
def require_no_authentication
super
# Delete flash message that isn't entirely useful and may be confusing in
# most cases because /web doesn't display/clear flash messages.
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
@@ -110,30 +91,13 @@ class Auth::SessionsController < Devise::SessionsController
def home_paths(resource)
paths = [about_path]
if single_user_mode? && resource.is_a?(User)
paths << short_account_path(username: resource.account)
end
paths
end
def continue_after?
truthy_param?(:continue)
end
def restart_session
clear_attempt_from_session
redirect_to new_user_session_path, alert: I18n.t('devise.failure.timeout')
end
def set_attempt_session(user)
session[:attempt_user_id] = user.id
session[:attempt_user_updated_at] = user.updated_at.to_s
end
def clear_attempt_from_session
session.delete(:attempt_user_id)
session.delete(:attempt_user_updated_at)
end
end

View File

@@ -29,24 +29,6 @@ module AccountOwnedConcern
end
def check_account_suspension
if @account.suspended_permanently?
permanent_suspension_response
elsif @account.suspended? && !skip_temporary_suspension_response?
temporary_suspension_response
end
end
def skip_temporary_suspension_response?
false
end
def permanent_suspension_response
expires_in(3.minutes, public: true)
gone
end
def temporary_suspension_response
expires_in(3.minutes, public: true)
forbidden
expires_in(3.minutes, public: true) && gone if @account.suspended?
end
end

View File

@@ -47,8 +47,4 @@ module CacheConcern
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
end
def cache_collection_paginated_by_id(raw, klass, limit, options)
cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass
end
end

View File

@@ -32,6 +32,7 @@ module ChallengableConcern
if params.key?(:form_challenge)
if challenge_passed?
session[:challenge_passed_at] = Time.now.utc
return
else
flash.now[:alert] = I18n.t('challenge.invalid_password')
render_challenge

View File

@@ -5,6 +5,7 @@ module ExportControllerConcern
included do
before_action :authenticate_user!
before_action :require_not_suspended!
before_action :load_export
skip_before_action :require_functional!
@@ -29,4 +30,8 @@ module ExportControllerConcern
def export_filename
"#{controller_name}.csv"
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
module RegistrationSpamConcern
extend ActiveSupport::Concern
def set_registration_form_time
session[:registration_form_time] = Time.now.utc
end
end

View File

@@ -18,9 +18,7 @@ module SignInTokenAuthenticationConcern
def authenticate_with_sign_in_token
user = self.resource = find_user
if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
restart_session
elsif user_params.key?(:sign_in_token_attempt) && session[:attempt_user_id]
if user_params[:sign_in_token_attempt].present? && session[:attempt_user_id]
authenticate_with_sign_in_token_attempt(user)
elsif user.present? && user.external_or_valid_password?(user_params[:password])
prompt_for_sign_in_token(user)
@@ -29,7 +27,7 @@ module SignInTokenAuthenticationConcern
def authenticate_with_sign_in_token_attempt(user)
if valid_sign_in_token_attempt?(user)
clear_attempt_from_session
session.delete(:attempt_user_id)
remember_me(user)
sign_in(user)
else
@@ -44,10 +42,10 @@ module SignInTokenAuthenticationConcern
UserMailer.sign_in_token(user, request.remote_ip, request.user_agent, Time.now.utc.to_s).deliver_later!
end
set_attempt_session(user)
@body_classes = 'lighter'
set_locale { render :sign_in_token }
set_locale do
session[:attempt_user_id] = user.id
@body_classes = 'lighter'
render :sign_in_token
end
end
end

View File

@@ -76,7 +76,6 @@ module SignatureVerification
raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window?
verify_signature_strength!
verify_body_digest!
account = account_from_key_id(signature_params['keyId'])
@@ -127,21 +126,12 @@ module SignatureVerification
def verify_signature_strength!
raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(Request::REQUEST_TARGET) || signed_headers.include?('digest')
raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
raise SignatureVerificationError, 'Mastodon requires the Host header to be signed' unless signed_headers.include?('host')
raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
end
def verify_body_digest!
return unless signed_headers.include?('digest')
digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] }
sha256 = digests.assoc('sha-256')
raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" if body_digest != sha256[1]
end
def verify_signature(account, signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
end
@@ -163,6 +153,8 @@ module SignatureVerification
raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
"(expires): #{signature_params['expires']}"
elsif signed_header == 'digest'
"digest: #{body_digest}"
else
"#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
end
@@ -195,7 +187,7 @@ module SignatureVerification
end
def body_digest
@body_digest ||= Digest::SHA256.base64digest(request_body)
"SHA-256=#{Digest::SHA256.base64digest(request_body)}"
end
def to_header_name(name)

View File

@@ -8,23 +8,7 @@ module TwoFactorAuthenticationConcern
end
def two_factor_enabled?
find_user&.two_factor_enabled?
end
def valid_webauthn_credential?(user, webauthn_credential)
user_credential = user.webauthn_credentials.find_by!(external_id: webauthn_credential.id)
begin
webauthn_credential.verify(
session[:webauthn_challenge],
public_key: user_credential.public_key,
sign_count: user_credential.sign_count
)
user_credential.update!(sign_count: webauthn_credential.sign_count)
rescue WebAuthn::Error
false
end
find_user&.otp_required_for_login?
end
def valid_otp_attempt?(user)
@@ -37,33 +21,16 @@ module TwoFactorAuthenticationConcern
def authenticate_with_two_factor
user = self.resource = find_user
if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
restart_session
elsif user.webauthn_enabled? && user_params.key?(:credential) && session[:attempt_user_id]
authenticate_with_two_factor_via_webauthn(user)
elsif user_params.key?(:otp_attempt) && session[:attempt_user_id]
authenticate_with_two_factor_via_otp(user)
if user_params[:otp_attempt].present? && session[:attempt_user_id]
authenticate_with_two_factor_attempt(user)
elsif user.present? && user.external_or_valid_password?(user_params[:password])
prompt_for_two_factor(user)
end
end
def authenticate_with_two_factor_via_webauthn(user)
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)
render json: { redirect_path: root_path }, status: :ok
else
render json: { error: t('webauthn_credentials.invalid_credential') }, status: :unprocessable_entity
end
end
def authenticate_with_two_factor_via_otp(user)
def authenticate_with_two_factor_attempt(user)
if valid_otp_attempt?(user)
clear_attempt_from_session
session.delete(:attempt_user_id)
remember_me(user)
sign_in(user)
else
@@ -73,18 +40,10 @@ module TwoFactorAuthenticationConcern
end
def prompt_for_two_factor(user)
set_attempt_session(user)
@body_classes = 'lighter'
@webauthn_enabled = user.webauthn_enabled?
@scheme_type = begin
if user.webauthn_enabled? && user_params[:otp_attempt].blank?
'webauthn'
else
'totp'
end
set_locale do
session[:attempt_user_id] = user.id
@body_classes = 'lighter'
render :two_factor
end
set_locale { render :two_factor }
end
end

View File

@@ -6,13 +6,14 @@ module UserTrackingConcern
UPDATE_SIGN_IN_HOURS = 24
included do
before_action :update_user_sign_in
before_action :set_user_activity
end
private
def update_user_sign_in
current_user.update_sign_in!(request) if user_needs_sign_in_update?
def set_user_activity
return unless user_needs_sign_in_update?
current_user.update_tracked_fields!(request)
end
def user_needs_sign_in_update?

View File

@@ -9,7 +9,7 @@ class FiltersController < ApplicationController
before_action :set_body_classes
def index
@filters = current_account.custom_filters.order(:phrase)
@filters = current_account.custom_filters
end
def new

View File

@@ -52,14 +52,6 @@ class FollowerAccountsController < ApplicationController
account_followers_url(@account, page: page) unless page.nil?
end
def next_page_url
page_url(follows.next_page) if follows.respond_to?(:next_page)
end
def prev_page_url
page_url(follows.prev_page) if follows.respond_to?(:prev_page)
end
def collection_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
@@ -68,8 +60,8 @@ class FollowerAccountsController < ApplicationController
size: @account.followers_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
part_of: account_followers_url(@account),
next: next_page_url,
prev: prev_page_url
next: page_url(follows.next_page),
prev: page_url(follows.prev_page)
)
else
ActivityPub::CollectionPresenter.new(

View File

@@ -52,14 +52,6 @@ class FollowingAccountsController < ApplicationController
account_following_index_url(@account, page: page) unless page.nil?
end
def next_page_url
page_url(follows.next_page) if follows.respond_to?(:next_page)
end
def prev_page_url
page_url(follows.prev_page) if follows.respond_to?(:prev_page)
end
def collection_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
@@ -68,8 +60,8 @@ class FollowingAccountsController < ApplicationController
size: @account.following_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
part_of: account_following_index_url(@account),
next: next_page_url,
prev: prev_page_url
next: page_url(follows.next_page),
prev: page_url(follows.prev_page)
)
else
ActivityPub::CollectionPresenter.new(

View File

@@ -17,6 +17,6 @@ class InstanceActorsController < ApplicationController
end
def restrict_fields_to
%i(id type preferred_username inbox outbox public_key endpoints url manually_approves_followers)
%i(id type preferred_username inbox public_key endpoints url manually_approves_followers)
end
end

View File

@@ -5,7 +5,6 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :store_current_location
before_action :authenticate_resource_owner!
before_action :require_not_suspended!, only: :destroy
before_action :set_body_classes
skip_before_action :require_functional!
@@ -26,8 +25,4 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
def store_current_location
store_location_for(:user, request.url)
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@@ -5,7 +5,6 @@ class RelationshipsController < ApplicationController
before_action :authenticate_user!
before_action :set_accounts, only: :show
before_action :set_relationships, only: :show
before_action :set_body_classes
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
@@ -29,10 +28,6 @@ class RelationshipsController < ApplicationController
@accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40)
end
def set_relationships
@relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id)
end
def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: [])
end
@@ -54,9 +49,7 @@ class RelationshipsController < ApplicationController
end
def action_from_button
if params[:follow]
'follow'
elsif params[:unfollow]
if params[:unfollow]
'unfollow'
elsif params[:remove_from_followers]
'remove_from_followers'

View File

@@ -1,9 +1,9 @@
# frozen_string_literal: true
class Settings::AliasesController < Settings::BaseController
skip_before_action :require_functional!
layout 'admin'
before_action :require_not_suspended!
before_action :authenticate_user!
before_action :set_aliases, except: :destroy
before_action :set_alias, only: :destroy

View File

@@ -1,6 +1,9 @@
# frozen_string_literal: true
class Settings::ApplicationsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update]

View File

@@ -1,9 +1,6 @@
# frozen_string_literal: true
class Settings::BaseController < ApplicationController
layout 'admin'
before_action :authenticate_user!
before_action :set_body_classes
before_action :set_cache_headers
@@ -16,8 +13,4 @@ class Settings::BaseController < ApplicationController
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@@ -1,10 +1,13 @@
# frozen_string_literal: true
class Settings::DeletesController < Settings::BaseController
skip_before_action :require_functional!
layout 'admin'
before_action :require_not_suspended!
before_action :check_enabled_deletion
before_action :authenticate_user!
before_action :require_not_suspended!
skip_before_action :require_functional!
def show
@confirmation = Form::DeleteConfirmation.new
@@ -42,8 +45,8 @@ class Settings::DeletesController < Settings::BaseController
end
def destroy_account!
current_account.suspend!(origin: :local)
AccountDeletionWorker.perform_async(current_user.account_id)
current_account.suspend!
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
sign_out
end
end

View File

@@ -2,7 +2,7 @@
module Settings
module Exports
class BlockedAccountsController < BaseController
class BlockedAccountsController < ApplicationController
include ExportControllerConcern
def index

View File

@@ -2,7 +2,7 @@
module Settings
module Exports
class BlockedDomainsController < BaseController
class BlockedDomainsController < ApplicationController
include ExportControllerConcern
def index

View File

@@ -1,19 +0,0 @@
# frozen_string_literal: true
module Settings
module Exports
class BookmarksController < BaseController
include ExportControllerConcern
def index
send_export_file
end
private
def export_data
@export.to_bookmarks_csv
end
end
end
end

View File

@@ -2,7 +2,7 @@
module Settings
module Exports
class FollowingAccountsController < BaseController
class FollowingAccountsController < ApplicationController
include ExportControllerConcern
def index

View File

@@ -2,7 +2,7 @@
module Settings
module Exports
class ListsController < BaseController
class ListsController < ApplicationController
include ExportControllerConcern
def index

View File

@@ -2,7 +2,7 @@
module Settings
module Exports
class MutedAccountsController < BaseController
class MutedAccountsController < ApplicationController
include ExportControllerConcern
def index

View File

@@ -3,6 +3,11 @@
class Settings::ExportsController < Settings::BaseController
include Authorization
layout 'admin'
before_action :authenticate_user!
before_action :require_not_suspended!
skip_before_action :require_functional!
def show
@@ -11,6 +16,8 @@ class Settings::ExportsController < Settings::BaseController
end
def create
raise Mastodon::NotPermittedError unless user_signed_in?
backup = nil
RedisLock.acquire(lock_options) do |lock|
@@ -30,4 +37,8 @@ class Settings::ExportsController < Settings::BaseController
def lock_options
{ redis: Redis.current, key: "backup:#{current_user.id}" }
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@@ -1,9 +1,12 @@
# frozen_string_literal: true
class Settings::FeaturedTagsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_featured_tags, only: :index
before_action :set_featured_tag, except: [:index, :create]
before_action :set_recently_used_tags, only: :index
before_action :set_most_used_tags, only: :index
def index
@featured_tag = FeaturedTag.new
@@ -17,7 +20,7 @@ class Settings::FeaturedTagsController < Settings::BaseController
redirect_to settings_featured_tags_path
else
set_featured_tags
set_recently_used_tags
set_most_used_tags
render :index
end
@@ -38,8 +41,8 @@ class Settings::FeaturedTagsController < Settings::BaseController
@featured_tags = current_account.featured_tags.order(statuses_count: :desc).reject(&:new_record?)
end
def set_recently_used_tags
@recently_used_tags = Tag.recently_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10)
def set_most_used_tags
@most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10)
end
def featured_tag_params

View File

@@ -1,6 +1,9 @@
# frozen_string_literal: true
class Settings::IdentityProofsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :check_required_params, only: :new
def index

View File

@@ -1,6 +1,9 @@
# frozen_string_literal: true
class Settings::ImportsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_account
def show

View File

@@ -1,10 +1,13 @@
# frozen_string_literal: true
class Settings::Migration::RedirectsController < Settings::BaseController
skip_before_action :require_functional!
layout 'admin'
before_action :authenticate_user!
before_action :require_not_suspended!
skip_before_action :require_functional!
def new
@redirect = Form::Redirect.new
end
@@ -35,4 +38,8 @@ class Settings::Migration::RedirectsController < Settings::BaseController
def resource_params
params.require(:form_redirect).permit(:acct, :current_password, :current_username)
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@@ -1,12 +1,15 @@
# frozen_string_literal: true
class Settings::MigrationsController < Settings::BaseController
skip_before_action :require_functional!
layout 'admin'
before_action :authenticate_user!
before_action :require_not_suspended!
before_action :set_migrations
before_action :set_cooldown
skip_before_action :require_functional!
def show
@migration = current_account.migrations.build
end
@@ -41,4 +44,8 @@ class Settings::MigrationsController < Settings::BaseController
def on_cooldown?
@cooldown.present?
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@@ -2,6 +2,7 @@
module Settings
class PicturesController < BaseController
before_action :authenticate_user!
before_action :set_account
before_action :set_picture

View File

@@ -1,6 +1,10 @@
# frozen_string_literal: true
class Settings::PreferencesController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
def show; end
def update
@@ -43,7 +47,6 @@ class Settings::PreferencesController < Settings::BaseController
:setting_display_media,
:setting_expand_spoilers,
:setting_reduce_motion,
:setting_disable_swiping,
:setting_system_font_ui,
:setting_noindex,
:setting_theme,

View File

@@ -1,6 +1,9 @@
# frozen_string_literal: true
class Settings::ProfilesController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_account
def show

View File

@@ -1,11 +1,11 @@
# frozen_string_literal: true
class Settings::SessionsController < Settings::BaseController
skip_before_action :require_functional!
before_action :require_not_suspended!
before_action :authenticate_user!
before_action :set_session, only: :destroy
skip_before_action :require_functional!
def destroy
@session.destroy!
flash[:notice] = I18n.t('sessions.revoke_success')

View File

@@ -5,31 +5,31 @@ module Settings
class ConfirmationsController < BaseController
include ChallengableConcern
skip_before_action :require_functional!
layout 'admin'
before_action :authenticate_user!
before_action :require_challenge!
before_action :ensure_otp_secret
skip_before_action :require_functional!
def new
prepare_two_factor_form
end
def create
if current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt], otp_secret: session[:new_otp_secret])
if current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt])
flash.now[:notice] = I18n.t('two_factor_authentication.enabled_success')
current_user.otp_required_for_login = true
current_user.otp_secret = session[:new_otp_secret]
@recovery_codes = current_user.generate_otp_backup_codes!
current_user.save!
UserMailer.two_factor_enabled(current_user).deliver_later!
session.delete(:new_otp_secret)
render 'settings/two_factor_authentication/recovery_codes/index'
else
flash.now[:alert] = I18n.t('otp_authentication.wrong_code')
flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')
prepare_two_factor_form
render :new
end
@@ -43,15 +43,12 @@ module Settings
def prepare_two_factor_form
@confirmation = Form::TwoFactorConfirmation.new
@new_otp_secret = session[:new_otp_secret]
@provision_url = current_user.otp_provisioning_uri(current_user.email,
otp_secret: @new_otp_secret,
issuer: Rails.configuration.x.local_domain)
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain)
@qrcode = RQRCode::QRCode.new(@provision_url)
end
def ensure_otp_secret
redirect_to settings_otp_authentication_path if session[:new_otp_secret].blank?
redirect_to settings_two_factor_authentication_path unless current_user.otp_secret
end
end
end

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