Commit 5f2c445c authored by khws4v1's avatar khws4v1

Merge tag 'v3.1.2' into ef67.myhome.cx

parents e1e784e1 aa8568b7
......@@ -3,6 +3,37 @@ Changelog
All notable changes to this project will be documented in this file.
## [v3.1.2] - 2020-02-27
### Added
- Add `--reset-password` option to `tootctl accounts modify` ([ThibG](https://github.com/tootsuite/mastodon/pull/13126))
- Add source-mapped stacktrace to error message in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13082))
### Fixed
- Fix dismissing an announcement twice raising an obscure error ([ThibG](https://github.com/tootsuite/mastodon/pull/13124))
- Fix misleading error when attempting to re-send a pending follow request ([ThibG](https://github.com/tootsuite/mastodon/pull/13133))
- Fix backups failing when files are missing from media attachments ([ThibG](https://github.com/tootsuite/mastodon/pull/13146))
- Fix duplicate accounts being created when fetching an account for its key only ([ThibG](https://github.com/tootsuite/mastodon/pull/13147))
- Fix `/web` redirecting to `/web/web` in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13128))
- Fix previously OStatus-based accounts not being detected as ActivityPub ([ThibG](https://github.com/tootsuite/mastodon/pull/13129))
- Fix account JSON/RSS not being cacheable due to wrong mime type comparison ([ThibG](https://github.com/tootsuite/mastodon/pull/13116))
- Fix old browsers crashing because of missing `finally` polyfill in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13115))
- Fix account's bio not being shown if there are no proofs/fields in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13075))
- Fix sign-ups without checked user agreement being accepted through the web form ([ThibG](https://github.com/tootsuite/mastodon/pull/13088))
- Fix non-x64 architectures not being able to build Docker image because of hardcoded Node.js architecture ([SaraSmiseth](https://github.com/tootsuite/mastodon/pull/13081))
- Fix invite request input not being shown on sign-up error if left empty ([ThibG](https://github.com/tootsuite/mastodon/pull/13089))
- Fix some migration hints mentioning GitLab instead of Mastodon ([saper](https://github.com/tootsuite/mastodon/pull/13084))
### Security
- Fix leak of arbitrary statuses through unfavourite action in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/13161))
## [3.1.1] - 2020-02-10
### Fixed
- Fix yanked dependency preventing installation ([mayaeh](https://github.com/tootsuite/mastodon/pull/13059))
## [3.1.0] - 2020-02-09
### Added
......
......@@ -5,14 +5,25 @@ SHELL ["bash", "-c"]
# Install Node v12 (LTS)
ENV NODE_VER="12.14.0"
RUN echo "Etc/UTC" > /etc/localtime && \
RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \
amd64) ARCH='x64';; \
ppc64el) ARCH='ppc64le';; \
s390x) ARCH='s390x';; \
arm64) ARCH='arm64';; \
armhf) ARCH='armv7l';; \
i386) ARCH='x86';; \
*) echo "unsupported architecture"; exit 1 ;; \
esac && \
echo "Etc/UTC" > /etc/localtime && \
apt update && \
apt -y install wget python && \
cd ~ && \
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-x64.tar.gz && \
tar xf node-v$NODE_VER-linux-x64.tar.gz && \
rm node-v$NODE_VER-linux-x64.tar.gz && \
mv node-v$NODE_VER-linux-x64 /opt/node
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
rm node-v$NODE_VER-linux-$ARCH.tar.gz && \
mv node-v$NODE_VER-linux-$ARCH /opt/node
# Install jemalloc
ENV JE_VER="5.2.1"
......
......@@ -9,7 +9,7 @@ gem 'puma', '~> 4.3'
gem 'rails', '~> 5.2.4'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 0.20'
gem 'rack', '~> 2.1.2'
gem 'rack', '~> 2.2.2'
gem 'thwait', '~> 0.1.0'
gem 'e2mmap', '~> 0.1.0'
......@@ -60,7 +60,7 @@ gem 'htmlentities', '~> 4.3'
gem 'http', '~> 4.3'
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.1'
gem 'httplog', '~> 1.4.2'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1'
gem 'link_header', '~> 0.0'
......@@ -101,14 +101,14 @@ gem 'webpacker', '~> 4.2'
gem 'webpush'
gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.0'
gem 'json-ld-preloaded', '~> 3.1'
gem 'rdf-normalize', '~> 0.4'
group :development, :test do
gem 'fabrication', '~> 2.21'
gem 'fuubar', '~> 2.5'
gem 'i18n-tasks', '~> 0.9', require: false
gem 'pry-byebug', '~> 3.7'
gem 'pry-byebug', '~> 3.8'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 3.9'
end
......@@ -118,13 +118,13 @@ group :production, :test do
end
group :test do
gem 'capybara', '~> 3.30'
gem 'capybara', '~> 3.31'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.10'
gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.17', require: false
gem 'simplecov', '~> 0.18', require: false
gem 'webmock', '~> 3.8'
gem 'parallel_tests', '~> 2.30'
end
......@@ -136,7 +136,7 @@ group :development do
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.1'
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3'
gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler'
gem 'rubocop', '~> 0.79', require: false
gem 'rubocop-rails', '~> 2.4', require: false
......
......@@ -127,7 +127,7 @@ GEM
bundler-audit (0.6.1)
bundler (>= 1.2.0, < 3)
thor (~> 0.18)
byebug (11.0.0)
byebug (11.1.1)
capistrano (3.11.2)
airbrussh (>= 1.0.0)
i18n
......@@ -144,7 +144,7 @@ GEM
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
capybara (3.30.0)
capybara (3.31.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
......@@ -282,7 +282,7 @@ GEM
http-parser (1.2.1)
ffi-compiler (>= 1.0, < 2.0)
http_accept_language (2.1.1)
httplog (1.4.1)
httplog (1.4.2)
rack (>= 1.0)
rainbow (>= 2.0.0)
i18n (1.8.2)
......@@ -311,10 +311,9 @@ GEM
multi_json (~> 1.14)
rack (~> 2.0)
rdf (~> 3.1)
json-ld-preloaded (3.0.6)
json-ld (~> 3.0)
multi_json (~> 1.12)
rdf (~> 3.0)
json-ld-preloaded (3.1.0)
json-ld (~> 3.1)
rdf (~> 3.1)
jsonapi-renderer (0.2.2)
jwt (2.1.0)
kaminari (1.1.1)
......@@ -333,7 +332,7 @@ GEM
addressable (~> 2.3)
letter_opener (1.7.0)
launchy (~> 2.2)
letter_opener_web (1.3.4)
letter_opener_web (1.4.0)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
......@@ -375,7 +374,7 @@ GEM
net-ssh (>= 2.6.5, < 6.0.0)
net-ssh (5.2.0)
nio4r (2.5.2)
nokogiri (1.10.7)
nokogiri (1.10.8)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.1)
nokogiri (~> 1.8, >= 1.8.4)
......@@ -418,7 +417,7 @@ GEM
pg (1.2.2)
pghero (2.4.1)
activerecord (>= 5)
pkg-config (1.4.0)
pkg-config (1.4.1)
premailer (1.11.1)
addressable
css_parser (>= 1.6.0)
......@@ -430,7 +429,7 @@ GEM
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pry-byebug (3.7.0)
pry-byebug (3.8.0)
byebug (~> 11.0)
pry (~> 0.10)
pry-rails (0.3.9)
......@@ -441,7 +440,7 @@ GEM
pundit (2.1.0)
activesupport (>= 3.0.0)
raabro (1.1.6)
rack (2.1.2)
rack (2.2.2)
rack-attack (6.2.2)
rack (>= 1.0, < 3)
rack-cors (1.1.1)
......@@ -550,7 +549,7 @@ GEM
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
rubocop-rails (2.4.1)
rubocop-rails (2.4.2)
rack (>= 1.1)
rubocop (>= 0.72.0)
ruby-progressbar (1.10.1)
......@@ -584,11 +583,10 @@ GEM
simple_form (5.0.1)
actionpack (>= 5.0)
activemodel (>= 5.0)
simplecov (0.17.1)
simplecov (0.18.2)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
simplecov-html (~> 0.11)
simplecov-html (0.12.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
......@@ -680,7 +678,7 @@ DEPENDENCIES
capistrano-rails (~> 1.4)
capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0)
capybara (~> 3.30)
capybara (~> 3.31)
charlock_holmes (~> 0.7.7)
chewy (~> 5.1)
cld3 (~> 3.2.6)
......@@ -709,15 +707,15 @@ DEPENDENCIES
http (~> 4.3)
http_accept_language (~> 2.1)
http_parser.rb (~> 0.6)!
httplog (~> 1.4.1)
httplog (~> 1.4.2)
i18n-tasks (~> 0.9)
idn-ruby
iso-639
json-ld
json-ld-preloaded (~> 3.0)
json-ld-preloaded (~> 3.1)
kaminari (~> 1.1)
letter_opener (~> 1.7)
letter_opener_web (~> 1.3)
letter_opener_web (~> 1.4)
link_header (~> 0.0)
lograge (~> 0.11)
makara (~> 0.4)
......@@ -745,11 +743,11 @@ DEPENDENCIES
posix-spawn!
premailer-rails
private_address_check (~> 0.5)
pry-byebug (~> 3.7)
pry-byebug (~> 3.8)
pry-rails (~> 0.3)
puma (~> 4.3)
pundit (~> 2.1)
rack (~> 2.1.2)
rack (~> 2.2.2)
rack-attack (~> 6.2)
rack-cors (~> 1.1)
rails (~> 5.2.4)
......@@ -773,7 +771,7 @@ DEPENDENCIES
sidekiq-unique-jobs (~> 6.0)
simple-navigation (~> 4.1)
simple_form (~> 5.0)
simplecov (~> 0.17)
simplecov (~> 0.18)
sprockets (~> 3.7.2)
sprockets-rails (~> 3.2)
stackprof
......
......@@ -9,7 +9,7 @@ class AccountsController < ApplicationController
before_action :set_cache_headers
before_action :set_body_classes
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format) }
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
skip_before_action :require_functional!
def show
......
......@@ -11,7 +11,7 @@ class Api::V1::AnnouncementsController < Api::BaseController
end
def dismiss
AnnouncementMute.create!(account: current_account, announcement: @announcement)
AnnouncementMute.find_or_create_by!(account: current_account, announcement: @announcement)
render_empty
end
......
......@@ -5,35 +5,28 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user!
before_action :set_status
respond_to :json
def create
@status = bookmarked_status
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
render json: @status, serializer: REST::StatusSerializer
end
def destroy
@status = requested_status
@bookmarks_map = { @status.id => false }
bookmark = current_account.bookmarks.find_by(status: @status)
bookmark&.destroy!
bookmark = Bookmark.find_by!(account: current_user.account, status: @status)
bookmark.destroy!
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, bookmarks_map: @bookmarks_map)
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
end
private
def bookmarked_status
authorize_with current_user.account, requested_status, :show?
bookmark = Bookmark.find_or_create_by!(account: current_user.account, status: requested_status)
bookmark.status.reload
end
def requested_status
Status.find(params[:status_id])
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end
......@@ -69,8 +69,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound
not_found
end
def pagination_params(core_params)
......
......@@ -5,34 +5,26 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user!
before_action :set_status
respond_to :json
def create
@status = favourited_status
FavouriteService.new.call(current_account, @status)
render json: @status, serializer: REST::StatusSerializer
end
def destroy
@status = requested_status
@favourites_map = { @status.id => false }
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, favourites_map: @favourites_map)
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 })
end
private
def favourited_status
service_result.status.reload
end
def service_result
FavouriteService.new.call(current_user.account, requested_status)
end
def requested_status
Status.find(params[:status_id])
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end
......@@ -66,8 +66,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound
not_found
end
def pagination_params(core_params)
......
......@@ -5,33 +5,34 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
before_action :require_user!
before_action :set_reblog
respond_to :json
def create
@status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params)
@status = ReblogService.new.call(current_account, @reblog, reblog_params)
render json: @status, serializer: REST::StatusSerializer
end
def destroy
@status = status_for_destroy.reblog
@reblogs_map = { @status.id => false }
@status = current_account.statuses.find_by(reblog_of_id: @reblog.id)
authorize status_for_destroy, :unreblog?
status_for_destroy.discard
RemovalWorker.perform_async(status_for_destroy.id)
if @status
authorize @status, :unreblog?
@status.discard
RemovalWorker.perform_async(@status.id)
end
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map)
render json: @reblog, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false })
end
private
def status_for_reblog
Status.find params[:status_id]
end
def status_for_destroy
@status_for_destroy ||= current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
def set_reblog
@reblog = Status.find(params[:status_id])
authorize @reblog, :show?
rescue Mastodon::NotPermittedError
not_found
end
def reblog_params
......
......@@ -41,7 +41,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.agreement = true
resource.current_sign_in_ip = request.remote_ip
resource.build_account if resource.account.nil?
......@@ -49,7 +48,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
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)
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement)
end
end
......
......@@ -6,6 +6,7 @@ import assign from 'object-assign';
import values from 'object.values';
import isNaN from 'is-nan';
import { decode as decodeBase64 } from './utils/base64';
import promiseFinally from 'promise.prototype.finally';
if (!Array.prototype.includes) {
includes.shim();
......@@ -23,6 +24,8 @@ if (!Number.isNaN) {
Number.isNaN = isNaN;
}
promiseFinally.shim();
if (!HTMLCanvasElement.prototype.toBlob) {
const BASE64_MARKER = ';base64,';
......
......@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { version, source_url } from 'mastodon/initial_state';
import StackTrace from 'stacktrace-js';
export default class ErrorBoundary extends React.PureComponent {
......@@ -11,24 +12,42 @@ export default class ErrorBoundary extends React.PureComponent {
state = {
hasError: false,
errorMessage: undefined,
stackTrace: undefined,
mappedStackTrace: undefined,
componentStack: undefined,
};
componentDidCatch (error, info) {
this.setState({
hasError: true,
errorMessage: error.toString(),
stackTrace: error.stack,
componentStack: info && info.componentStack,
copied: false,
mappedStackTrace: undefined,
});
StackTrace.fromError(error).then((stackframes) => {
this.setState({
mappedStackTrace: stackframes.map((sf) => sf.toString()).join('\n'),
});
}).catch(() => {
this.setState({
mappedStackTrace: undefined,
});
});
}
handleCopyStackTrace = () => {
const { stackTrace } = this.state;
const { errorMessage, stackTrace, mappedStackTrace } = this.state;
const textarea = document.createElement('textarea');
textarea.textContent = stackTrace;
let contents = [errorMessage, stackTrace];
if (mappedStackTrace) {
contents.push(mappedStackTrace);
}
textarea.textContent = contents.join('\n\n\n');
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
......
......@@ -18,7 +18,8 @@ function loadPolyfills() {
Number.isNaN &&
Object.assign &&
Object.values &&
window.Symbol
window.Symbol &&
Promise.prototype.finally
);
// Latest version of Firefox and Safari do not have IntersectionObserver.
......
......@@ -12,7 +12,7 @@ function main() {
if (window.history && history.replaceState) {
const { pathname, search, hash } = window.location;
const path = pathname + search + hash;
if (!(/^\/web[$/]/).test(path)) {
if (!(/^\/web($|\/)/).test(path)) {
history.replaceState(null, document.title, `/web${path}`);
}
}
......
......@@ -18,9 +18,10 @@ class ActivityPub::ProcessAccountService < BaseService
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
@account = Account.find_remote(@username, @domain)
@old_public_key = @account&.public_key
@old_protocol = @account&.protocol
@account = Account.remote.find_by(uri: @uri) if @options[:only_key]
@account ||= Account.find_remote(@username, @domain)
@old_public_key = @account&.public_key
@old_protocol = @account&.protocol
create_account if @account.nil?
update_account
......
......@@ -66,6 +66,8 @@ class BackupService < BaseService
def dump_media_attachments!(tar)
MediaAttachment.attached.where(account: account).reorder(nil).find_in_batches do |media_attachments|
media_attachments.each do |m|
next unless m.file&.path
download_to_tar(tar, m.file, m.file.path)
end
......
......@@ -18,14 +18,13 @@ class FollowService < BaseService
if source_account.following?(target_account)
# We're already following this account, but we'll call follow! again to
# make sure the reblogs status is set correctly.
source_account.follow!(target_account, reblogs: reblogs)
return
return source_account.follow!(target_account, reblogs: reblogs)
elsif source_account.requested?(target_account)
# This isn't managed by a method in AccountInteractions, so we modify it
# ourselves if necessary.
req = source_account.follow_requests.find_by(target_account: target_account)
req.update!(show_reblogs: reblogs)
return
return req
end
ActivityTracker.increment('activity:interactions')
......
......@@ -99,7 +99,7 @@ class ResolveAccountService < BaseService
if lock.acquired?
@account = Account.find_remote(@username, @domain)
next if (@account.present? && !@account.activitypub?) || actor_json.nil?
next if actor_json.nil?
@account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
else
......
......@@ -27,9 +27,9 @@
= fa_icon 'check'
= Formatter.instance.format_field(account, field.value, custom_emojify: true)
- if account.note.present?
%div
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
- if account.note.present?
%div
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
.dashboard__counters{ style: 'margin-top: 10px' }
%div
......
......@@ -27,7 +27,7 @@
- if approved_registrations? && !@invite.present?
.fields-group
= f.simple_fields_for :invite_request do |invite_request_fields|
= f.simple_fields_for :invite_request, resource.invite_request || resource.build_invite_request do |invite_request_fields|
= invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: false
= f.input :invite_code, as: :hidden
......
......@@ -10,6 +10,12 @@
%strong
= t('authorize_follow.already_following')
= render 'post_follow_actions'
- elsif current_account.requested?(@resource)
.flash-message
%strong
= t('authorize_follow.already_requested')
= render 'post_follow_actions'
- else
= form_tag authorize_interaction_path, method: :post, class: 'simple_form' do
......
......@@ -661,6 +661,7 @@ en:
trouble_logging_in: Trouble logging in?
authorize_follow:
already_following: You are already following this account
already_requested: You have already sent a follow request to that account
error: Unfortunately, there was an error looking up the remote account
follow: Follow
follow_request: 'You have sent a follow request to:'
......
......@@ -120,6 +120,7 @@ module Mastodon
option :disable, type: :boolean
option :disable_2fa, type: :boolean
option :approve, type: :boolean
option :reset_password, type: :boolean
desc 'modify USERNAME', 'Modify a user'
long_desc <<-LONG_DESC
Modify a user account.
......@@ -138,6 +139,9 @@ module Mastodon
With the --disable-2fa option, the two-factor authentication
requirement for the user can be removed.
With the --reset-password option, the user's password is replaced by
a randomly-generated one, printed in the output.
LONG_DESC
def modify(username)
user = Account.find_local(username)&.user
......@@ -152,6 +156,8 @@ module Mastodon
user.moderator = options[:role] == 'moderator'
end
password = SecureRandom.hex if options[:reset_password]
user.password = password if options[:reset_password]
user.email = options[:email] if options[:email]
user.disabled = false if options[:enable]
user.disabled = true if options[:disable]
......@@ -161,6 +167,7 @@ module Mastodon
if user.save
say('OK', :green)
say("New password: #{password}") if options[:reset_password]
else
user.errors.to_h.each do |key, error|
say('Failure/Error: ', :red)
......
......@@ -886,16 +886,12 @@ module Mastodon
Your database user is not allowed to create, drop, or execute triggers on the
table #{table}.
If you are using PostgreSQL you can solve this by logging in to the GitLab
If you are using PostgreSQL you can solve this by logging in to the Mastodon
database (#{dbname}) using a super user and running:
ALTER USER #{user} WITH SUPERUSER
For MySQL you instead need to run:
GRANT ALL PRIVILEGES ON *.* TO #{user}@'%'