2013-02-14 12:57:26 -05:00
require_dependency 'discourse_hub'
2013-06-06 16:40:10 +02:00
require_dependency 'user_name_suggester'
2013-11-11 23:21:14 +05:30
require_dependency 'avatar_upload_service'
2014-09-18 10:48:56 -04:00
require_dependency 'rate_limiter'
2013-02-05 14:16:51 -05:00
class UsersController < ApplicationController
2013-08-14 12:20:05 +02:00
skip_before_filter :authorize_mini_profiler , only : [ :avatar ]
2014-09-23 19:20:57 +05:30
skip_before_filter :check_xhr , only : [ :show , :password_reset , :update , :account_created , :activate_account , :perform_account_activation , :authorize_email , :user_preferences_redirect , :avatar , :my_redirect ]
2013-02-05 14:16:51 -05:00
2014-06-27 14:48:39 -04:00
before_filter :ensure_logged_in , only : [ :username , :update , :change_email , :user_preferences_redirect , :upload_user_image , :pick_avatar , :destroy_user_image , :destroy ]
2013-11-12 14:37:38 -08:00
before_filter :respond_to_suspicious_request , only : [ :create ]
2013-02-07 16:45:24 +01:00
2013-05-22 11:20:16 -04:00
# we need to allow account creation with bad CSRF tokens, if people are caching, the CSRF token on the
2013-05-03 16:43:11 +10:00
# page is going to be empty, this means that server will see an invalid CSRF and blow the session
# once that happens you can't log in with social
2013-06-05 14:01:24 +10:00
skip_before_filter :verify_authenticity_token , only : [ :create ]
2013-07-15 12:12:54 -04:00
skip_before_filter :redirect_to_login_if_required , only : [ :check_username ,
:create ,
:get_honeypot_value ,
2014-09-23 19:20:57 +05:30
:account_created ,
2013-07-15 12:12:54 -04:00
:activate_account ,
2014-07-15 10:47:47 -07:00
:perform_account_activation ,
2013-07-15 12:12:54 -04:00
:send_activation_email ,
:authorize_email ,
:password_reset ]
2013-05-03 16:43:11 +10:00
2013-02-07 16:45:24 +01:00
def show
2013-02-05 14:16:51 -05:00
@user = fetch_user_from_params
2013-03-08 15:04:37 -05:00
user_serializer = UserSerializer . new ( @user , scope : guardian , root : 'user' )
respond_to do | format |
format . html do
store_preloaded ( " user_ #{ @user . username } " , MultiJson . dump ( user_serializer ) )
end
format . json do
render_json_dump ( user_serializer )
end
2013-02-05 14:16:51 -05:00
end
end
def user_preferences_redirect
redirect_to email_preferences_path ( current_user . username_lower )
end
def update
2013-10-07 11:19:45 +01:00
user = fetch_user_from_params
2013-02-05 14:16:51 -05:00
guardian . ensure_can_edit! ( user )
2014-09-08 15:17:31 -04:00
json_result ( user , serializer : UserSerializer , additional_errors : [ :user_profile ] ) do | u |
2013-12-10 12:46:35 -05:00
updater = UserUpdater . new ( current_user , user )
2013-11-01 14:06:20 -07:00
updater . update ( params )
2013-02-07 16:45:24 +01:00
end
2013-02-05 14:16:51 -05:00
end
def username
2013-06-06 00:14:32 -07:00
params . require ( :new_username )
2013-02-05 14:16:51 -05:00
user = fetch_user_from_params
2013-08-12 14:54:52 -04:00
guardian . ensure_can_edit_username! ( user )
2013-02-07 16:45:24 +01:00
2013-02-05 14:16:51 -05:00
result = user . change_username ( params [ :new_username ] )
raise Discourse :: InvalidParameters . new ( :new_username ) unless result
2013-02-07 16:45:24 +01:00
render nothing : true
2013-02-05 14:16:51 -05:00
end
2014-04-18 08:40:53 +05:30
def badge_title
params . require ( :user_badge_id )
user = fetch_user_from_params
guardian . ensure_can_edit! ( user )
2014-07-14 18:06:50 +10:00
user_badge = UserBadge . find_by ( id : params [ :user_badge_id ] )
if user_badge && user_badge . user == user && user_badge . badge . allow_title?
2014-04-18 08:40:53 +05:30
user . title = user_badge . badge . name
user . save!
2014-07-14 18:06:50 +10:00
else
user . title = ''
user . save!
2014-04-18 08:40:53 +05:30
end
render nothing : true
end
2013-02-05 14:16:51 -05:00
def preferences
render nothing : true
end
2014-04-21 11:52:11 -04:00
def my_redirect
2014-06-14 10:51:06 -07:00
if current_user . present? && params [ :path ] =~ / ^[a-z \ - \/ ]+$ /
2014-04-21 11:52:11 -04:00
redirect_to " /users/ #{ current_user . username } / #{ params [ :path ] } "
return
end
raise Discourse :: NotFound . new
end
2013-02-05 14:16:51 -05:00
def invited
2013-11-08 11:11:41 -08:00
inviter = fetch_user_from_params
2014-08-01 16:36:31 +05:30
offset = params [ :offset ] . to_i || 0
2013-11-05 17:52:50 -05:00
2014-03-21 14:13:04 -04:00
invites = if guardian . can_see_invite_details? ( inviter )
2014-08-01 16:36:31 +05:30
Invite . find_all_invites_from ( inviter , offset )
2013-11-08 11:11:41 -08:00
else
2014-08-01 16:36:31 +05:30
Invite . find_redeemed_invites_from ( inviter , offset )
2013-11-05 17:52:50 -05:00
end
2013-11-08 11:11:41 -08:00
invites = invites . filter_by ( params [ :filter ] )
2014-03-21 14:13:04 -04:00
render_json_dump invites : serialize_data ( invites . to_a , InviteSerializer ) ,
can_see_invite_details : guardian . can_see_invite_details? ( inviter )
2013-02-05 14:16:51 -05:00
end
def is_local_username
2013-06-06 00:14:32 -07:00
params . require ( :username )
2013-02-05 14:16:51 -05:00
u = params [ :username ] . downcase
r = User . exec_sql ( 'select 1 from users where username_lower = ?' , u ) . values
render json : { valid : r . length == 1 }
end
2013-08-24 22:57:12 -10:00
def render_available_true
render ( json : { available : true } )
end
def changing_case_of_own_username ( target_user , username )
target_user and username . downcase == target_user . username . downcase
end
# Used for checking availability of a username and will return suggestions
# if the username is not available.
2013-02-05 14:16:51 -05:00
def check_username
2013-11-19 14:15:05 -05:00
if ! params [ :username ] . present?
params . require ( :username ) if ! params [ :email ] . present?
2014-07-16 12:25:24 -04:00
return render ( json : success_json )
2013-11-19 14:15:05 -05:00
end
2013-08-24 22:57:12 -10:00
username = params [ :username ]
2013-02-05 14:16:51 -05:00
2013-08-24 22:57:12 -10:00
target_user = user_from_params_or_current_user
2013-07-30 14:13:56 -04:00
2013-06-28 16:21:46 -04:00
# The special case where someone is changing the case of their own username
2013-08-24 22:57:12 -10:00
return render_available_true if changing_case_of_own_username ( target_user , username )
2013-06-28 16:21:46 -04:00
2013-09-06 09:35:29 +00:00
checker = UsernameCheckerService . new
email = params [ :email ] || target_user . try ( :email )
2013-11-19 14:15:05 -05:00
render json : checker . check_username ( username , email )
2013-08-24 22:57:12 -10:00
end
2013-02-05 14:16:51 -05:00
2013-08-24 22:57:12 -10:00
def user_from_params_or_current_user
params [ :for_user_id ] ? User . find ( params [ :for_user_id ] ) : current_user
end
2013-02-05 14:16:51 -05:00
def create
2014-07-14 15:42:14 -04:00
unless SiteSetting . allow_new_registrations
render json : { success : false , message : I18n . t ( " login.new_registrations_disabled " ) }
return
end
2014-09-11 12:22:11 -07:00
if params [ :password ] && params [ :password ] . length > User . max_password_length
render json : { success : false , message : I18n . t ( " login.password_too_long " ) }
return
end
2013-11-12 14:37:38 -08:00
user = User . new ( user_params )
2013-02-06 19:25:21 -05:00
2013-11-12 14:37:38 -08:00
authentication = UserAuthenticator . new ( user , session )
2014-03-26 15:39:44 +11:00
if ! authentication . has_authenticator? && ! SiteSetting . enable_local_logins
render nothing : true , status : 500
return
end
2013-11-12 14:37:38 -08:00
authentication . start
2013-02-05 14:16:51 -05:00
2013-11-12 14:37:38 -08:00
activation = UserActivator . new ( user , request , session , cookies )
activation . start
2013-02-05 14:16:51 -05:00
2014-03-20 14:49:25 +11:00
# just assign a password if we have an authenticator and no password
# this is the case for Twitter
user . password = SecureRandom . hex if user . password . blank? && authentication . has_authenticator?
2013-11-12 14:37:38 -08:00
if user . save
authentication . finish
activation . finish
render json : {
success : true ,
active : user . active? ,
2014-09-23 09:06:19 +10:00
message : activation . message ,
user_id : user . id
2013-11-12 14:37:38 -08:00
}
else
render json : {
success : false ,
message : I18n . t (
'login.errors' ,
errors : user . errors . full_messages . join ( " \n " )
) ,
errors : user . errors . to_hash ,
values : user . attributes . slice ( 'name' , 'username' , 'email' )
}
end
2013-03-07 14:56:28 -05:00
rescue ActiveRecord :: StatementInvalid
2013-11-12 14:37:38 -08:00
render json : {
success : false ,
message : I18n . t ( " login.something_already_taken " )
}
2013-02-05 14:16:51 -05:00
rescue RestClient :: Forbidden
2013-04-13 00:46:55 +02:00
render json : { errors : [ I18n . t ( " discourse_hub.access_token_problem " ) ] }
2013-02-05 14:16:51 -05:00
end
2013-02-06 19:25:21 -05:00
def get_honeypot_value
render json : { value : honeypot_value , challenge : challenge_value }
end
2013-02-05 14:16:51 -05:00
def password_reset
expires_now ( )
2014-08-25 15:30:52 -04:00
if EmailToken . valid_token_format? ( params [ :token ] )
@user = EmailToken . confirm ( params [ :token ] )
if @user
session [ " password- #{ params [ :token ] } " ] = @user . id
else
user_id = session [ " password- #{ params [ :token ] } " ]
@user = User . find ( user_id ) if user_id
end
2014-07-02 13:06:55 +10:00
else
2014-08-25 15:30:52 -04:00
@invalid_token = true
2014-07-02 13:06:55 +10:00
end
if ! @user
2013-02-05 14:16:51 -05:00
flash [ :error ] = I18n . t ( 'password_reset.no_token' )
2013-11-11 22:28:26 +11:00
elsif request . put?
2014-09-11 12:22:11 -07:00
@invalid_password = params [ :password ] . blank? || params [ :password ] . length > User . max_password_length
if @invalid_password
@user . errors . add ( :password , :invalid )
else
@user . password = params [ :password ]
@user . password_required!
if @user . save
Invite . invalidate_for_email ( @user . email ) # invite link can't be used to log in anymore
logon_after_password_reset
end
2014-01-21 16:53:46 -05:00
end
2013-02-05 14:16:51 -05:00
end
2013-03-22 14:08:11 -04:00
render layout : 'no_js'
2013-02-05 14:16:51 -05:00
end
2013-02-07 16:45:24 +01:00
2013-10-07 11:19:45 +01:00
def logon_after_password_reset
2013-11-11 23:21:14 +05:30
message = if Guardian . new ( @user ) . can_access_forum?
# Log in the user
log_on_user ( @user )
'password_reset.success'
else
@requires_approval = true
'password_reset.success_unapproved'
end
flash [ :success ] = I18n . t ( message )
2014-07-03 17:29:44 +10:00
end
2013-10-07 11:19:45 +01:00
2013-02-05 14:16:51 -05:00
def change_email
2013-06-06 00:14:32 -07:00
params . require ( :email )
2013-02-05 14:16:51 -05:00
user = fetch_user_from_params
2013-09-08 11:42:41 +09:00
guardian . ensure_can_edit_email! ( user )
2013-04-27 23:02:23 -04:00
lower_email = Email . downcase ( params [ :email ] ) . strip
2013-02-05 14:16:51 -05:00
2014-09-18 10:48:56 -04:00
RateLimiter . new ( user , " change-email-hr- #{ request . remote_ip } " , 6 , 1 . hour ) . performed!
RateLimiter . new ( user , " change-email-min- #{ request . remote_ip } " , 3 , 1 . minute ) . performed!
2013-02-05 14:16:51 -05:00
# Raise an error if the email is already in use
2014-07-14 10:16:24 -04:00
if User . find_by_email ( lower_email )
2013-04-13 00:46:55 +02:00
raise Discourse :: InvalidParameters . new ( :email )
end
2013-02-05 14:16:51 -05:00
2013-04-15 02:20:33 +02:00
email_token = user . email_tokens . create ( email : lower_email )
2013-04-13 00:46:55 +02:00
Jobs . enqueue (
:user_email ,
2013-04-15 02:20:33 +02:00
to_address : lower_email ,
2013-04-13 00:46:55 +02:00
type : :authorize_email ,
user_id : user . id ,
email_token : email_token . token
)
2013-02-05 14:16:51 -05:00
2013-02-07 16:45:24 +01:00
render nothing : true
2014-09-18 10:48:56 -04:00
rescue RateLimiter :: LimitExceeded
render_json_error ( I18n . t ( " rate_limiter.slow_down " ) )
2013-02-05 14:16:51 -05:00
end
def authorize_email
expires_now ( )
if @user = EmailToken . confirm ( params [ :token ] )
log_on_user ( @user )
else
flash [ :error ] = I18n . t ( 'change_email.error' )
end
2013-03-22 14:08:11 -04:00
render layout : 'no_js'
2013-02-05 14:16:51 -05:00
end
2014-09-23 19:20:57 +05:30
def account_created
expires_now
render layout : 'no_js'
end
2013-02-05 14:16:51 -05:00
def activate_account
2014-07-15 10:47:47 -07:00
expires_now
2014-07-14 12:25:42 -04:00
render layout : 'no_js'
end
def perform_account_activation
2014-07-15 14:07:19 -04:00
raise Discourse :: InvalidAccess . new if honeypot_or_challenge_fails? ( params )
2013-02-05 14:16:51 -05:00
if @user = EmailToken . confirm ( params [ :token ] )
# Log in the user unless they need to be approved
2013-04-03 12:23:28 -04:00
if Guardian . new ( @user ) . can_access_forum?
2013-02-05 14:16:51 -05:00
@user . enqueue_welcome_message ( 'welcome_user' ) if @user . send_welcome_message
log_on_user ( @user )
2013-04-03 12:23:28 -04:00
else
@needs_approval = true
2013-02-05 14:16:51 -05:00
end
else
flash [ :error ] = I18n . t ( 'activation.already_done' )
end
2013-03-22 14:08:11 -04:00
render layout : 'no_js'
2013-02-05 14:16:51 -05:00
end
2013-02-22 11:49:48 -05:00
def send_activation_email
2014-08-28 12:07:13 -04:00
@user = fetch_user_from_params ( include_inactive : true )
2013-02-22 11:49:48 -05:00
@email_token = @user . email_tokens . unconfirmed . active . first
2013-10-07 11:19:45 +01:00
enqueue_activation_email if @user
2013-02-22 11:49:48 -05:00
render nothing : true
end
2013-10-07 11:19:45 +01:00
def enqueue_activation_email
@email_token || = @user . email_tokens . create ( email : @user . email )
Jobs . enqueue ( :user_email , type : :signup , user_id : @user . id , email_token : @email_token . token )
end
2013-02-05 14:16:51 -05:00
def search_users
2013-02-07 05:59:25 -05:00
term = params [ :term ] . to_s . strip
2013-02-05 14:16:51 -05:00
topic_id = params [ :topic_id ]
topic_id = topic_id . to_i if topic_id
2014-05-13 11:44:06 -04:00
results = UserSearch . new ( term , topic_id : topic_id , searching_user : current_user ) . search
2013-02-05 14:16:51 -05:00
2014-05-29 14:59:14 +10:00
user_fields = [ :username , :upload_avatar_template , :uploaded_avatar_id ]
2013-10-30 15:45:13 -04:00
user_fields << :name if SiteSetting . enable_names?
2013-12-23 15:46:00 +01:00
to_render = { users : results . as_json ( only : user_fields , methods : :avatar_template ) }
if params [ :include_groups ] == " true "
to_render [ :groups ] = Group . search_group ( term , current_user ) . map { | m | { :name = > m . name , :usernames = > m . usernames . split ( " , " ) } }
end
render json : to_render
2013-02-05 14:16:51 -05:00
end
2013-08-14 12:20:05 +02:00
# [LEGACY] avatars in quotes/oneboxes might still be pointing to this route
# fixing it requires a rebake of all the posts
2013-08-13 22:08:29 +02:00
def avatar
2014-05-06 14:41:59 +01:00
user = User . find_by ( username_lower : params [ :username ] . downcase )
2013-08-14 12:20:05 +02:00
if user . present?
size = determine_avatar_size ( params [ :size ] )
url = user . avatar_template . gsub ( " {size} " , size . to_s )
expires_in 1 . day
redirect_to url
else
raise ActiveRecord :: RecordNotFound
end
end
def determine_avatar_size ( size )
size = size . to_i
size = 64 if size == 0
size = 10 if size < 10
size = 128 if size > 128
size
end
2014-04-14 22:55:57 +02:00
# LEGACY: used by the API
2013-08-14 12:20:05 +02:00
def upload_avatar
2014-06-26 11:43:44 -04:00
params [ :image_type ] = " avatar "
2014-02-28 21:12:51 +01:00
upload_user_image
end
2014-04-14 22:55:57 +02:00
2014-02-28 21:12:51 +01:00
def upload_user_image
2014-06-26 11:43:44 -04:00
params . require ( :image_type )
2013-08-13 22:08:29 +02:00
user = fetch_user_from_params
guardian . ensure_can_edit! ( user )
file = params [ :file ] || params [ :files ] . first
2014-04-14 22:55:57 +02:00
begin
image = build_user_image_from ( file )
rescue Discourse :: InvalidParameters
return render status : 422 , text : I18n . t ( " upload.images.unknown_image_type " )
2013-11-11 23:21:14 +05:30
end
2013-08-13 22:08:29 +02:00
2014-04-14 22:55:57 +02:00
upload = Upload . create_for ( user . id , image . file , image . filename , image . filesize )
if upload . errors . empty?
2014-06-26 11:43:44 -04:00
case params [ :image_type ]
2014-04-14 22:55:57 +02:00
when " avatar "
upload_avatar_for ( user , upload )
when " profile_background "
2014-06-11 18:52:50 -07:00
upload_profile_background_for ( user . user_profile , upload )
2014-04-14 22:55:57 +02:00
end
2014-02-28 21:12:51 +01:00
else
2014-04-14 22:55:57 +02:00
render status : 422 , text : upload . errors . full_messages
2014-02-28 21:12:51 +01:00
end
2013-08-13 22:08:29 +02:00
end
2014-05-26 19:46:43 +10:00
def pick_avatar
2013-08-13 22:08:29 +02:00
user = fetch_user_from_params
guardian . ensure_can_edit! ( user )
2014-05-26 19:46:43 +10:00
upload_id = params [ :upload_id ]
2013-08-13 22:08:29 +02:00
2014-05-26 19:46:43 +10:00
user . uploaded_avatar_id = upload_id
# ensure we associate the custom avatar properly
2014-05-30 14:37:12 +10:00
if upload_id && ! user . user_avatar . contains_upload? ( upload_id )
2014-05-26 19:46:43 +10:00
user . user_avatar . custom_upload_id = upload_id
end
2013-08-13 22:08:29 +02:00
user . save!
2013-08-17 00:29:54 +02:00
render nothing : true
2013-08-13 22:08:29 +02:00
end
2014-04-14 22:55:57 +02:00
2014-06-27 14:48:39 -04:00
def destroy_user_image
2014-02-28 21:12:51 +01:00
user = fetch_user_from_params
guardian . ensure_can_edit! ( user )
2014-04-14 22:55:57 +02:00
2014-06-27 14:48:39 -04:00
image_type = params . require ( :image_type )
if image_type == 'profile_background'
user . user_profile . clear_profile_background
else
raise Discourse :: InvalidParameters . new ( :image_type )
end
2014-04-14 22:55:57 +02:00
2014-02-28 21:12:51 +01:00
render nothing : true
end
2014-04-14 22:55:57 +02:00
2014-02-13 11:42:35 -05:00
def destroy
@user = fetch_user_from_params
guardian . ensure_can_delete_user! ( @user )
2014-04-14 22:55:57 +02:00
2014-02-13 11:42:35 -05:00
UserDestroyer . new ( current_user ) . destroy ( @user , { delete_posts : true , context : params [ :context ] } )
2014-04-14 22:55:57 +02:00
2014-02-13 11:42:35 -05:00
render json : success_json
end
2014-07-11 17:32:29 +10:00
def read_faq
if ( user = current_user )
user . user_stat . read_faq = 1 . second . ago
user . user_stat . save
end
render json : success_json
end
2013-02-05 14:16:51 -05:00
private
2013-02-06 19:25:21 -05:00
def honeypot_value
Digest :: SHA1 :: hexdigest ( " #{ Discourse . current_hostname } : #{ Discourse :: Application . config . secret_token } " ) [ 0 , 15 ]
end
def challenge_value
2013-08-23 16:19:23 +10:00
challenge = $redis . get ( 'SECRET_CHALLENGE' )
unless challenge && challenge . length == 16 * 2
challenge = SecureRandom . hex ( 16 )
$redis . set ( 'SECRET_CHALLENGE' , challenge )
end
challenge
2013-02-06 19:25:21 -05:00
end
2014-02-28 21:12:51 +01:00
def build_user_image_from ( file )
2013-11-11 23:21:14 +05:30
source = if file . is_a? ( String )
2014-04-14 22:55:57 +02:00
is_api? ? :url : ( raise Discourse :: InvalidParameters )
2013-11-11 23:21:14 +05:30
else
:image
end
2014-04-14 22:55:57 +02:00
2013-11-11 23:21:14 +05:30
AvatarUploadService . new ( file , source )
end
2014-04-14 22:55:57 +02:00
def upload_avatar_for ( user , upload )
2014-05-26 19:46:43 +10:00
render json : { upload_id : upload . id , url : upload . url , width : upload . width , height : upload . height }
2013-11-11 23:21:14 +05:30
end
2014-04-14 22:55:57 +02:00
2014-06-11 18:52:50 -07:00
def upload_profile_background_for ( user_profile , upload )
user_profile . upload_profile_background ( upload )
2014-04-14 22:55:57 +02:00
# TODO: add a resize job here
2014-02-28 21:12:51 +01:00
render json : { url : upload . url , width : upload . width , height : upload . height }
end
2014-04-14 22:55:57 +02:00
2013-11-12 14:37:38 -08:00
def respond_to_suspicious_request
if suspicious? ( params )
render (
json : {
success : true ,
active : false ,
message : I18n . t ( " login.activate_email " , email : params [ :email ] )
}
)
end
end
def suspicious? ( params )
2014-09-23 09:06:19 +10:00
return false if current_user && is_api? && current_user . admin?
2013-11-12 14:37:38 -08:00
honeypot_or_challenge_fails? ( params ) || SiteSetting . invite_only?
end
def honeypot_or_challenge_fails? ( params )
2014-09-23 09:06:19 +10:00
return false if is_api?
2013-11-12 14:37:38 -08:00
params [ :password_confirmation ] != honeypot_value ||
params [ :challenge ] != challenge_value . try ( :reverse )
end
def user_params
params . permit (
:name ,
:email ,
:password ,
2014-02-04 15:40:30 -05:00
:username ,
:active
2014-04-29 14:37:56 -04:00
) . merge ( ip_address : request . ip , registration_ip_address : request . ip )
2013-11-12 14:37:38 -08:00
end
2013-02-05 14:16:51 -05:00
end