FIX: Passkey login when Discourse used as SSO provider (#28672)

Co-authored-by: Osama Sayegh <asooomaasoooma90@gmail.com>
This commit is contained in:
Penar Musaraj 2024-09-03 11:46:23 -04:00 committed by GitHub
parent 393c2f61ea
commit 8c19104866
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 9 deletions

View File

@ -131,7 +131,12 @@ export default class Login extends Component {
if (authResult && !authResult.error) {
const destinationUrl = cookie("destination_url");
if (destinationUrl) {
const ssoDestinationUrl = cookie("sso_destination_url");
if (ssoDestinationUrl) {
removeCookie("sso_destination_url");
window.location.assign(ssoDestinationUrl);
} else if (destinationUrl) {
removeCookie("destination_url");
window.location.assign(destinationUrl);
} else {

View File

@ -75,6 +75,7 @@ class SessionController < ApplicationController
if request.xhr?
# for the login modal
cookies[:sso_destination_url] = data[:sso_redirect_url]
render json: success_json.merge(redirect_url: data[:sso_redirect_url])
else
redirect_to data[:sso_redirect_url], allow_other_host: true
end
@ -370,7 +371,7 @@ class SessionController < ApplicationController
return render(json: @second_factor_failure_payload) if !second_factor_auth_result.ok
if user.active && user.email_confirmed?
login(user, second_factor_auth_result)
login(user, second_factor_auth_result: second_factor_auth_result)
else
not_activated(user)
end
@ -396,7 +397,7 @@ class SessionController < ApplicationController
user = User.where(id: security_key.user_id, active: true).first
if user.email_confirmed?
login(user, false)
login(user, passkey_login: true)
else
not_activated(user)
end
@ -798,17 +799,18 @@ class SessionController < ApplicationController
{ error: user.suspended_message, reason: "suspended" }
end
def login(user, second_factor_auth_result)
def login(user, passkey_login: false, second_factor_auth_result: nil)
session.delete(ACTIVATE_USER_KEY)
user.update_timezone_if_missing(params[:timezone])
log_on_user(user)
if payload = cookies.delete(:sso_payload)
confirmed_2fa_during_login =
(
second_factor_auth_result&.ok && second_factor_auth_result.used_2fa_method.present? &&
second_factor_auth_result.used_2fa_method != UserSecondFactor.methods[:backup_codes]
)
passkey_login ||
(
second_factor_auth_result&.ok && second_factor_auth_result.used_2fa_method.present? &&
second_factor_auth_result.used_2fa_method != UserSecondFactor.methods[:backup_codes]
)
sso_provider(payload, confirmed_2fa_during_login)
else
render_serialized(user, UserSerializer)

View File

@ -1802,7 +1802,7 @@ RSpec.describe SessionController do
},
xhr: true,
headers: headers
expect(response.status).to eq(204)
expect(response.status).to eq(200)
# the frontend will take care of actually redirecting the user
redirect_url = response.cookies["sso_destination_url"]
expect(redirect_url).to start_with("http://somewhere.over.rainbow/sso?sso=")
@ -3252,6 +3252,43 @@ RSpec.describe SessionController do
expect(session[:current_user_id]).to eq(user.id)
end
context "with a valid discourse connect provider" do
before do
sso = DiscourseConnectBase.new
sso.nonce = "mynonce"
sso.sso_secret = "topsecret"
sso.return_sso_url = "http://somewhere.over.rainbow/sso"
cookies[:sso_payload] = sso.payload
provider_uid = 12_345
UserAssociatedAccount.create!(
provider_name: "google_oauth2",
provider_uid: provider_uid,
user: user,
)
SiteSetting.enable_discourse_connect_provider = true
SiteSetting.discourse_connect_secret = sso.sso_secret
SiteSetting.discourse_connect_provider_secrets =
"somewhere.over.rainbow|#{sso.sso_secret}"
end
it "logs the user in" do
simulate_localhost_passkey_challenge
user.activate
user.create_or_fetch_secure_identifier
post "/session/passkey/auth.json",
params: {
publicKeyCredential:
valid_passkey_auth_data.merge(
{ userHandle: Base64.strict_encode64(user.secure_identifier) },
),
}
expect(response.status).to eq(302)
expect(response.location).to start_with("http://somewhere.over.rainbow/sso")
end
end
end
end
end