mirror of
https://github.com/discourse/discourse.git
synced 2025-05-24 03:36:18 +08:00
DEV: Allow run_second_factor!
to be used before login (#25420)
In a handful of situations, we need to verify a user's 2fa credentials before `current_user` is assigned. For example: login, email_login and change-email confirmation. This commit adds an explicit `target_user:` parameter to the centralized 2fa system so that it can be used for those situations. For safety and clarity, this new parameter only works for anon. If some user is logged in, and target_user is set to a different user, an exception will be raised.
This commit is contained in:
@ -11,8 +11,6 @@ class SessionController < ApplicationController
|
||||
|
||||
skip_before_action :check_xhr, only: %i[second_factor_auth_show]
|
||||
|
||||
requires_login only: %i[second_factor_auth_show second_factor_auth_perform]
|
||||
|
||||
allow_in_staff_writes_only_mode :create
|
||||
allow_in_staff_writes_only_mode :email_login
|
||||
|
||||
@ -47,8 +45,10 @@ class SessionController < ApplicationController
|
||||
result =
|
||||
run_second_factor!(
|
||||
SecondFactor::Actions::DiscourseConnectProvider,
|
||||
payload: payload,
|
||||
confirmed_2fa_during_login: confirmed_2fa_during_login,
|
||||
action_data: {
|
||||
payload: payload,
|
||||
confirmed_2fa_during_login: confirmed_2fa_during_login,
|
||||
},
|
||||
)
|
||||
|
||||
if result.second_factor_auth_skipped?
|
||||
@ -136,8 +136,10 @@ class SessionController < ApplicationController
|
||||
skip_before_action :check_xhr, only: :test_second_factor_restricted_route
|
||||
|
||||
def test_second_factor_restricted_route
|
||||
target_user = User.find_by_username(params[:username]) || current_user
|
||||
raise "user required" if !target_user
|
||||
result =
|
||||
run_second_factor!(TestSecondFactorAction) do |manager|
|
||||
run_second_factor!(TestSecondFactorAction, target_user: target_user) do |manager|
|
||||
manager.allow_backup_codes! if params[:allow_backup_codes]
|
||||
end
|
||||
if result.no_second_factors_enabled?
|
||||
@ -145,6 +147,15 @@ class SessionController < ApplicationController
|
||||
else
|
||||
render json: { result: "second_factor_auth_completed" }
|
||||
end
|
||||
rescue StandardError => e
|
||||
# Normally this would be checked by the consumer before calling `run_second_factor!`
|
||||
# but since this is a test route, we allow passing a bad value into the API, catch the error
|
||||
# and return a JSON response to assert against.
|
||||
if e.message == "running 2fa against another user is not allowed"
|
||||
render json: { result: "wrong user" }, status: 400
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -464,14 +475,18 @@ class SessionController < ApplicationController
|
||||
end
|
||||
|
||||
def second_factor_auth_show
|
||||
user = current_user
|
||||
|
||||
nonce = params.require(:nonce)
|
||||
challenge = nil
|
||||
error_key = nil
|
||||
user = nil
|
||||
status_code = 200
|
||||
begin
|
||||
challenge = SecondFactor::AuthManager.find_second_factor_challenge(nonce, secure_session)
|
||||
challenge =
|
||||
SecondFactor::AuthManager.find_second_factor_challenge(
|
||||
nonce: nonce,
|
||||
secure_session: secure_session,
|
||||
target_user: current_user,
|
||||
)
|
||||
rescue SecondFactor::BadChallenge => exception
|
||||
error_key = exception.error_translation_key
|
||||
status_code = exception.status_code
|
||||
@ -479,6 +494,7 @@ class SessionController < ApplicationController
|
||||
|
||||
json = {}
|
||||
if challenge
|
||||
user = User.find(challenge[:target_user_id])
|
||||
json.merge!(
|
||||
totp_enabled: user.totp_enabled?,
|
||||
backup_enabled: user.backup_codes_enabled?,
|
||||
@ -510,9 +526,16 @@ class SessionController < ApplicationController
|
||||
nonce = params.require(:nonce)
|
||||
challenge = nil
|
||||
error_key = nil
|
||||
user = nil
|
||||
status_code = 200
|
||||
begin
|
||||
challenge = SecondFactor::AuthManager.find_second_factor_challenge(nonce, secure_session)
|
||||
challenge =
|
||||
SecondFactor::AuthManager.find_second_factor_challenge(
|
||||
nonce: nonce,
|
||||
secure_session: secure_session,
|
||||
target_user: current_user,
|
||||
)
|
||||
user = User.find(challenge[:target_user_id])
|
||||
rescue SecondFactor::BadChallenge => exception
|
||||
error_key = exception.error_translation_key
|
||||
status_code = exception.status_code
|
||||
@ -535,7 +558,7 @@ class SessionController < ApplicationController
|
||||
# they're redirected to the 2fa page and then uses the same method they've
|
||||
# disabled.
|
||||
second_factor_method = params[:second_factor_method].to_i
|
||||
if !current_user.valid_second_factor_method_for_user?(second_factor_method)
|
||||
if !user.valid_second_factor_method_for_user?(second_factor_method)
|
||||
raise Discourse::InvalidAccess.new
|
||||
end
|
||||
# and this happens if someone tries to use a 2FA method that's not accepted
|
||||
@ -546,8 +569,8 @@ class SessionController < ApplicationController
|
||||
end
|
||||
|
||||
if !challenge[:successful]
|
||||
rate_limit_second_factor!(current_user)
|
||||
second_factor_auth_result = current_user.authenticate_second_factor(params, secure_session)
|
||||
rate_limit_second_factor!(user)
|
||||
second_factor_auth_result = user.authenticate_second_factor(params, secure_session)
|
||||
if second_factor_auth_result.ok
|
||||
challenge[:successful] = true
|
||||
challenge[:generated_at] += 1.minute.to_i
|
||||
|
Reference in New Issue
Block a user