Skip to content

Commit

Permalink
Add ability to skip sign-in token authentication for specific users (m…
Browse files Browse the repository at this point in the history
…astodon#16427)

Remove "active within last two weeks" exception for sign in token requirement

Change admin reset password to lock access until the password is reset
  • Loading branch information
Gargron authored Jul 8, 2021
1 parent 2e0eac7 commit 771c9d4
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 32 deletions.
4 changes: 2 additions & 2 deletions app/controllers/admin/resets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class ResetsController < BaseController

def create
authorize @user, :reset_password?
@user.send_reset_password_instructions
@user.reset_password!
log_action :reset_password, @user
redirect_to admin_accounts_path
redirect_to admin_account_path(@user.account_id)
end
end
end
27 changes: 27 additions & 0 deletions app/controllers/admin/sign_in_token_authentications_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Admin
class SignInTokenAuthenticationsController < BaseController
before_action :set_target_user

def create
authorize @user, :enable_sign_in_token_auth?
@user.update(skip_sign_in_token: false)
log_action :enable_sign_in_token_auth, @user
redirect_to admin_account_path(@user.account_id)
end

def destroy
authorize @user, :disable_sign_in_token_auth?
@user.update(skip_sign_in_token: true)
log_action :disable_sign_in_token_auth, @user
redirect_to admin_account_path(@user.account_id)
end

private

def set_target_user
@user = User.find(params[:user_id])
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def destroy
@user.disable_two_factor!
log_action :disable_2fa, @user
UserMailer.two_factor_disabled(@user).deliver_later!
redirect_to admin_accounts_path
redirect_to admin_account_path(@user.account_id)
end

private
Expand Down
25 changes: 23 additions & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
# sign_in_token_sent_at :datetime
# webauthn_id :string
# sign_up_ip :inet
# skip_sign_in_token :boolean
#

class User < ApplicationRecord
Expand Down Expand Up @@ -200,7 +201,7 @@ def active_for_authentication?
end

def suspicious_sign_in?(ip)
!otp_required_for_login? && current_sign_in_at.present? && current_sign_in_at < 2.weeks.ago && !recent_ip?(ip)
!otp_required_for_login? && !skip_sign_in_token? && current_sign_in_at.present? && !recent_ip?(ip)
end

def functional?
Expand Down Expand Up @@ -329,12 +330,32 @@ def send_reset_password_instructions
super
end

def reset_password!(new_password, new_password_confirmation)
def reset_password(new_password, new_password_confirmation)
return false if encrypted_password.blank?

super
end

def reset_password!
# First, change password to something random, invalidate the remember-me token,
# and deactivate all sessions
transaction do
update(remember_token: nil, remember_created_at: nil, password: SecureRandom.hex)
session_activations.destroy_all
end

# Then, remove all authorized applications and connected push subscriptions
Doorkeeper::AccessGrant.by_resource_owner(self).in_batches.update_all(revoked_at: Time.now.utc)

Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch|
batch.update_all(revoked_at: Time.now.utc)
Web::PushSubscription.where(access_token_id: batch).delete_all
end

# Finally, send a reset password prompt to the user
send_reset_password_instructions
end

def show_all_media?
setting_display_media == 'show_all'
end
Expand Down
8 changes: 8 additions & 0 deletions app/policies/user_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ def disable_2fa?
admin? && !record.staff?
end

def disable_sign_in_token_auth?
staff?
end

def enable_sign_in_token_auth?
staff?
end

def confirm?
staff? && !record.confirmed?
end
Expand Down
24 changes: 21 additions & 3 deletions app/views/admin/accounts/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@
- else
= t('admin.accounts.confirming')
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
%tr
%th{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }= t('admin.accounts.security')
%td{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }
- if @account.user&.two_factor_enabled?
= t 'admin.accounts.security_measures.password_and_2fa'
- elsif @account.user&.skip_sign_in_token?
= t 'admin.accounts.security_measures.only_password'
- else
= t 'admin.accounts.security_measures.password_and_sign_in_token'
%td
- if @account.user&.two_factor_enabled?
= table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete if can?(:disable_2fa, @account.user)
- elsif @account.user&.skip_sign_in_token?
= table_link_to 'lock', t('admin.accounts.enable_sign_in_token_auth'), admin_user_sign_in_token_authentication_path(@account.user.id), method: :post if can?(:enable_sign_in_token_auth, @account.user)
- else
= table_link_to 'unlock', t('admin.accounts.disable_sign_in_token_auth'), admin_user_sign_in_token_authentication_path(@account.user.id), method: :delete if can?(:disable_sign_in_token_auth, @account.user)

- if can?(:reset_password, @account.user)
%tr
%td
= table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') }

%tr
%th= t('simple_form.labels.defaults.locale')
Expand Down Expand Up @@ -221,9 +242,6 @@

%div
- if @account.local?
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
- if @account.user&.otp_required_for_login?
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
- if [email protected]? && @account.user_approved?
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
- else
Expand Down
42 changes: 26 additions & 16 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ en:
rejecting_media: 'Media files from these servers will not be processed or stored, and no thumbnails will be displayed, requiring manual click-through to the original file:'
rejecting_media_title: Filtered media
silenced: 'Posts from these servers will be hidden in public timelines and conversations, and no notifications will be generated from their users interactions, unless you are following them:'
silenced_title: Silenced servers
silenced_title: Limited servers
suspended: 'No data from these servers will be processed, stored or exchanged, making any interaction or communication with users from these servers impossible:'
suspended_title: Suspended servers
unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.
Expand Down Expand Up @@ -119,6 +119,7 @@ en:
demote: Demote
destroyed_msg: "%{username}'s data is now queued to be deleted imminently"
disable: Freeze
disable_sign_in_token_auth: Disable e-mail token authentication
disable_two_factor_authentication: Disable 2FA
disabled: Frozen
display_name: Display name
Expand All @@ -127,6 +128,7 @@ en:
email: Email
email_status: Email status
enable: Unfreeze
enable_sign_in_token_auth: Enable e-mail token authentication
enabled: Enabled
enabled_msg: Successfully unfroze %{username}'s account
followers: Followers
Expand All @@ -151,7 +153,7 @@ en:
active: Active
all: All
pending: Pending
silenced: Silenced
silenced: Limited
suspended: Suspended
title: Moderation
moderation_notes: Moderation notes
Expand Down Expand Up @@ -191,8 +193,12 @@ en:
search: Search
search_same_email_domain: Other users with the same e-mail domain
search_same_ip: Other users with the same IP
sensitive: Sensitive
sensitized: marked as sensitive
security_measures:
only_password: Only password
password_and_2fa: Password and 2FA
password_and_sign_in_token: Password and e-mail token
sensitive: Force-sensitive
sensitized: Marked as sensitive
shared_inbox_url: Shared inbox URL
show:
created_reports: Made reports
Expand All @@ -207,10 +213,10 @@ en:
time_in_queue: Waiting in queue %{time}
title: Accounts
unconfirmed_email: Unconfirmed email
undo_sensitized: Undo sensitive
undo_silenced: Undo silence
undo_sensitized: Undo force-sensitive
undo_silenced: Undo limit
undo_suspension: Undo suspension
unsilenced_msg: Successfully unlimited %{username}'s account
unsilenced_msg: Successfully undid limit of %{username}'s account
unsubscribe: Unsubscribe
unsuspended_msg: Successfully unsuspended %{username}'s account
username: Username
Expand All @@ -236,27 +242,29 @@ en:
destroy_custom_emoji: Delete Custom Emoji
destroy_domain_allow: Delete Domain Allow
destroy_domain_block: Delete Domain Block
destroy_email_domain_block: Delete e-mail domain block
destroy_email_domain_block: Delete E-mail Domain Block
destroy_ip_block: Delete IP rule
destroy_status: Delete Post
destroy_unavailable_domain: Delete Unavailable Domain
disable_2fa_user: Disable 2FA
disable_custom_emoji: Disable Custom Emoji
disable_sign_in_token_auth_user: Disable E-mail Token Authentication for User
disable_user: Disable User
enable_custom_emoji: Enable Custom Emoji
enable_sign_in_token_auth_user: Enable E-mail Token Authentication for User
enable_user: Enable User
memorialize_account: Memorialize Account
promote_user: Promote User
remove_avatar_user: Remove Avatar
reopen_report: Reopen Report
reset_password_user: Reset Password
resolve_report: Resolve Report
sensitive_account: Mark the media in your account as sensitive
silence_account: Silence Account
sensitive_account: Force-Sensitive Account
silence_account: Limit Account
suspend_account: Suspend Account
unassigned_report: Unassign Report
unsensitive_account: Unmark the media in your account as sensitive
unsilence_account: Unsilence Account
unsensitive_account: Undo Force-Sensitive Account
unsilence_account: Undo Limit Account
unsuspend_account: Unsuspend Account
update_announcement: Update Announcement
update_custom_emoji: Update Custom Emoji
Expand Down Expand Up @@ -285,8 +293,10 @@ en:
destroy_unavailable_domain_html: "%{name} resumed delivery to domain %{target}"
disable_2fa_user_html: "%{name} disabled two factor requirement for user %{target}"
disable_custom_emoji_html: "%{name} disabled emoji %{target}"
disable_sign_in_token_auth_user_html: "%{name} disabled e-mail token authentication for %{target}"
disable_user_html: "%{name} disabled login for user %{target}"
enable_custom_emoji_html: "%{name} enabled emoji %{target}"
enable_sign_in_token_auth_user_html: "%{name} enabled e-mail token authentication for %{target}"
enable_user_html: "%{name} enabled login for user %{target}"
memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page"
promote_user_html: "%{name} promoted user %{target}"
Expand All @@ -295,11 +305,11 @@ en:
reset_password_user_html: "%{name} reset password of user %{target}"
resolve_report_html: "%{name} resolved report %{target}"
sensitive_account_html: "%{name} marked %{target}'s media as sensitive"
silence_account_html: "%{name} silenced %{target}'s account"
silence_account_html: "%{name} limited %{target}'s account"
suspend_account_html: "%{name} suspended %{target}'s account"
unassigned_report_html: "%{name} unassigned report %{target}"
unsensitive_account_html: "%{name} unmarked %{target}'s media as sensitive"
unsilence_account_html: "%{name} unsilenced %{target}'s account"
unsilence_account_html: "%{name} undid limit of %{target}'s account"
unsuspend_account_html: "%{name} unsuspended %{target}'s account"
update_announcement_html: "%{name} updated announcement %{target}"
update_custom_emoji_html: "%{name} updated emoji %{target}"
Expand Down Expand Up @@ -421,14 +431,14 @@ en:
rejecting_media: rejecting media files
rejecting_reports: rejecting reports
severity:
silence: silenced
silence: limited
suspend: suspended
show:
affected_accounts:
one: One account in the database affected
other: "%{count} accounts in the database affected"
retroactive:
silence: Unsilence existing affected accounts from this domain
silence: Undo limit of existing affected accounts from this domain
suspend: Unsuspend existing affected accounts from this domain
title: Undo domain block for %{domain}
undo: Undo
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@

resources :users, only: [] do
resource :two_factor_authentication, only: [:destroy]
resource :sign_in_token_authentication, only: [:create, :destroy]
end

resources :custom_emojis, only: [:index, :new, :create] do
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddSkipSignInTokenToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :skip_sign_in_token, :boolean
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@
t.datetime "sign_in_token_sent_at"
t.string "webauthn_id"
t.inet "sign_up_ip"
t.boolean "skip_sign_in_token"
t.index ["account_id"], name: "index_users_on_account_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id"
Expand Down
15 changes: 12 additions & 3 deletions lib/mastodon/accounts_cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def rotate(username = nil)

option :email, required: true
option :confirmed, type: :boolean
option :role, default: 'user'
option :role, default: 'user', enum: %w(user moderator admin)
option :skip_sign_in_token, type: :boolean
option :reattach, type: :boolean
option :force, type: :boolean
desc 'create USERNAME', 'Create a new user'
Expand All @@ -68,6 +69,9 @@ def rotate(username = nil)
With the --role option one of "user", "admin" or "moderator"
can be supplied. Defaults to "user"
With the --skip-sign-in-token option, you can ensure that
the user is never asked for an e-mailed security code.
With the --reattach option, the new user will be reattached
to a given existing username of an old account. If the old
account is still in use by someone else, you can supply
Expand All @@ -77,7 +81,7 @@ def rotate(username = nil)
def create(username)
account = Account.new(username: username)
password = SecureRandom.hex
user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true)
user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true, skip_sign_in_token: options[:skip_sign_in_token])

if options[:reattach]
account = Account.find_local(username) || Account.new(username: username)
Expand Down Expand Up @@ -113,14 +117,15 @@ def create(username)
end
end

option :role
option :role, enum: %w(user moderator admin)
option :email
option :confirm, type: :boolean
option :enable, type: :boolean
option :disable, type: :boolean
option :disable_2fa, type: :boolean
option :approve, type: :boolean
option :reset_password, type: :boolean
option :skip_sign_in_token, type: :boolean
desc 'modify USERNAME', 'Modify a user'
long_desc <<-LONG_DESC
Modify a user account.
Expand All @@ -142,6 +147,9 @@ def create(username)
With the --reset-password option, the user's password is replaced by
a randomly-generated one, printed in the output.
With the --skip-sign-in-token option, you can ensure that
the user is never asked for an e-mailed security code.
LONG_DESC
def modify(username)
user = Account.find_local(username)&.user
Expand All @@ -163,6 +171,7 @@ def modify(username)
user.disabled = true if options[:disable]
user.approved = true if options[:approve]
user.otp_required_for_login = false if options[:disable_2fa]
user.skip_sign_in_token = options[:skip_sign_in_token] unless options[:skip_sign_in_token].nil?
user.confirm if options[:confirm]

if user.save
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/admin/resets_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

post :create, params: { account_id: account.id }

expect(response).to redirect_to(admin_accounts_path)
expect(response).to redirect_to(admin_account_path(account.id))
end
end
end
Loading

0 comments on commit 771c9d4

Please sign in to comment.