DEV: Rename SecureSession to ServerSession

This patch will be followed by
https://github.com/discourse/discourse/pull/34747.

`SecureSession` doesn’t make a lot of sense anymore and can be confusing
as the current cookie store used for the session is actually secure
since it’s encrypted.

Renaming it to `ServerSession` better conveys what it does: providing a
session but on the server side only.

This patch also makes some improvements, like injecting that server
session into Rack-like request objects, allowing the server session to
be available virtually everywhere.
This commit is contained in:
Loïc Guitaut
2025-09-18 16:31:03 +02:00
committed by Loïc Guitaut
parent 281d0c3e11
commit b4e4833d2a
17 changed files with 145 additions and 136 deletions
+3 -4
View File
@@ -14,6 +14,9 @@ class ApplicationController < ActionController::Base
attr_reader :theme_id
delegate :server_session, to: :request
alias_method :secure_session, :server_session
serialization_scope :guardian
protect_from_forgery
@@ -568,10 +571,6 @@ class ApplicationController < ActionController::Base
request.session_options[:skip] = true
end
def secure_session
SecureSession.new(session["secure_session_id"] ||= SecureRandom.hex)
end
def handle_permalink(path)
permalink = Permalink.find_by_url(path)
if permalink && permalink.target_url
+4 -4
View File
@@ -36,7 +36,7 @@ class SessionController < ApplicationController
session.delete(:destination_url)
cookies.delete(:destination_url)
sso = DiscourseConnect.generate_sso(return_path, secure_session: secure_session)
sso = DiscourseConnect.generate_sso(return_path, secure_session:)
connect_verbose_warn { "Verbose SSO log: Started SSO process\n\n#{sso.diagnostics}" }
redirect_to sso_url(sso), allow_other_host: true
end
@@ -698,13 +698,13 @@ class SessionController < ApplicationController
end
def get_honeypot_value
secure_session.set(HONEYPOT_KEY, honeypot_value, expires: 1.hour)
secure_session.set(CHALLENGE_KEY, challenge_value, expires: 1.hour)
server_session[HONEYPOT_KEY] = honeypot_value
server_session[CHALLENGE_KEY] = challenge_value
render json: {
value: honeypot_value,
challenge: challenge_value,
expires_in: SecureSession.expiry,
expires_in: ServerSession.expiry,
}
end
+10 -10
View File
@@ -67,10 +67,10 @@ module DiscourseWebauthn
# credentials.
#
# @param user [User] the user to stage the challenge for
# @param secure_session [SecureSession] the session to store the challenge in
def self.stage_challenge(user, secure_session)
# @param server_session [ServerSession] the session to store the challenge in
def self.stage_challenge(user, server_session)
::DiscourseWebauthn::ChallengeGenerator.generate.commit_to_session(
secure_session,
server_session,
user,
expires: CHALLENGE_EXPIRY,
)
@@ -80,22 +80,22 @@ module DiscourseWebauthn
# Clears the challenge from the user's secure session.
#
# @param user [User] the user to clear the challenge for
# @param secure_session [SecureSession] the session to clear the challenge from
def self.clear_challenge(user, secure_session)
secure_session[self.session_challenge_key(user)] = nil
# @param server_session [ServerSession] the session to clear the challenge from
def self.clear_challenge(user, server_session)
server_session[self.session_challenge_key(user)] = nil
end
def self.allowed_credentials(user, secure_session)
def self.allowed_credentials(user, server_session)
return {} if !user.security_keys_enabled?
{
allowed_credential_ids: user.second_factor_security_key_credential_ids,
challenge: self.challenge(user, secure_session),
challenge: self.challenge(user, server_session),
}
end
def self.challenge(user, secure_session)
secure_session[self.session_challenge_key(user)]
def self.challenge(user, server_session)
server_session[self.session_challenge_key(user)]
end
def self.rp_id
@@ -8,8 +8,8 @@ module DiscourseWebauthn
@challenge = params[:challenge]
end
def commit_to_session(secure_session, user, expires: nil)
secure_session.set(DiscourseWebauthn.session_challenge_key(user), @challenge, expires:)
def commit_to_session(server_session, user, expires: server_session.expiry)
server_session.set(DiscourseWebauthn.session_challenge_key(user), @challenge, expires:)
self
end
end
@@ -0,0 +1,10 @@
# frozen_string_literal: true
module RequestServerSession
def server_session
session[:server_session_id] ||= (session.delete(:secure_session_id) || SecureRandom.hex)
ServerSession.new(session[:server_session_id])
end
end
Rack::Request::Helpers.prepend(RequestServerSession)
+16 -13
View File
@@ -1,21 +1,24 @@
# frozen_string_literal: true
# session that is not stored in cookie, expires after 1.hour unconditionally
class SecureSession
# Session that is not stored in cookie, expires after 1.hour unconditionally
class ServerSession
delegate :expiry, to: :class
class << self
def expiry
@expiry ||= 1.hour.to_i
end
def expiry=(val)
@expiry = val
end
end
def initialize(prefix)
@prefix = prefix
end
def self.expiry
@expiry ||= 1.hour.to_i
end
def self.expiry=(val)
@expiry = val
end
def set(key, val, expires: nil)
expires ||= SecureSession.expiry
def set(key, val, expires: expiry)
Discourse.redis.setex(prefixed_key(key), expires.to_i, val.to_s)
true
end
@@ -32,7 +35,7 @@ class SecureSession
if val == nil
Discourse.redis.del(prefixed_key(key))
else
Discourse.redis.setex(prefixed_key(key), SecureSession.expiry.to_i, val.to_s)
Discourse.redis.setex(prefixed_key(key), expiry.to_i, val.to_s)
end
end
+27 -26
View File
@@ -165,16 +165,17 @@ RSpec.describe SecondFactorManager do
describe "#authenticate_second_factor" do
let(:params) { {} }
let(:secure_session) { SecureSession.new("some-prefix") }
let(:server_session) { ServerSession.new("some-prefix") }
context "when neither security keys nor totp/backup codes are enabled" do
before { disable_security_key && disable_totp }
it "returns OK, because it doesn't need to authenticate" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "keeps used_2fa_method nil because no authentication is done" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(nil)
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(nil)
end
end
@@ -182,7 +183,7 @@ RSpec.describe SecondFactorManager do
before do
disable_totp
simulate_localhost_webauthn_challenge
DiscourseWebauthn.stage_challenge(user, secure_session)
DiscourseWebauthn.stage_challenge(user, server_session)
DiscourseWebauthn.stubs(:origin).returns("http://localhost:3000")
end
@@ -194,11 +195,11 @@ RSpec.describe SecondFactorManager do
}
end
it "returns OK" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "sets used_2fa_method to security keys" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(
UserSecondFactor.methods[:security_key],
)
end
@@ -217,7 +218,7 @@ RSpec.describe SecondFactorManager do
}
end
it "returns not OK" do
result = user.authenticate_second_factor(params, secure_session)
result = user.authenticate_second_factor(params, server_session)
expect(result.ok).to eq(false)
expect(result.error).to eq(I18n.t("webauthn.validation.not_found_error"))
expect(result.used_2fa_method).to eq(nil)
@@ -236,11 +237,11 @@ RSpec.describe SecondFactorManager do
}
end
it "returns OK" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "sets used_2fa_method to totp" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(
UserSecondFactor.methods[:totp],
)
end
@@ -251,7 +252,7 @@ RSpec.describe SecondFactorManager do
{ second_factor_token: "blah", second_factor_method: UserSecondFactor.methods[:totp] }
end
it "returns not OK" do
result = user.authenticate_second_factor(params, secure_session)
result = user.authenticate_second_factor(params, server_session)
expect(result.ok).to eq(false)
expect(result.error).to eq(I18n.t("login.invalid_second_factor_code"))
expect(result.used_2fa_method).to eq(nil)
@@ -265,13 +266,13 @@ RSpec.describe SecondFactorManager do
before do
simulate_localhost_webauthn_challenge
DiscourseWebauthn.stage_challenge(user, secure_session)
DiscourseWebauthn.stage_challenge(user, server_session)
DiscourseWebauthn.stubs(:origin).returns("http://localhost:3000")
end
context "when method selected is invalid" do
it "returns an error" do
result = user.authenticate_second_factor(params, secure_session)
result = user.authenticate_second_factor(params, server_session)
expect(result.ok).to eq(false)
expect(result.error).to eq(I18n.t("login.invalid_second_factor_method"))
expect(result.used_2fa_method).to eq(nil)
@@ -286,11 +287,11 @@ RSpec.describe SecondFactorManager do
let(:params) { { second_factor_token: token, second_factor_method: method } }
it "validates totp OK" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "sets used_2fa_method to totp" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(
UserSecondFactor.methods[:totp],
)
end
@@ -300,7 +301,7 @@ RSpec.describe SecondFactorManager do
before { user.totps.destroy_all }
it "returns an error" do
result = user.authenticate_second_factor(params, secure_session)
result = user.authenticate_second_factor(params, server_session)
expect(result.ok).to eq(false)
expect(result.error).to eq(I18n.t("login.not_enabled_second_factor_method"))
expect(result.used_2fa_method).to eq(nil)
@@ -314,7 +315,7 @@ RSpec.describe SecondFactorManager do
before do
simulate_localhost_webauthn_challenge
DiscourseWebauthn.stage_challenge(user, secure_session)
DiscourseWebauthn.stage_challenge(user, server_session)
end
context "when security key params are valid" do
@@ -322,11 +323,11 @@ RSpec.describe SecondFactorManager do
{ second_factor_token: valid_security_key_auth_post_data, second_factor_method: method }
end
it "returns OK" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "sets used_2fa_method to security keys" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(
UserSecondFactor.methods[:security_key],
)
end
@@ -335,7 +336,7 @@ RSpec.describe SecondFactorManager do
before { user.security_keys.destroy_all }
it "returns an error" do
result = user.authenticate_second_factor(params, secure_session)
result = user.authenticate_second_factor(params, server_session)
expect(result.ok).to eq(false)
expect(result.error).to eq(I18n.t("login.not_enabled_second_factor_method"))
expect(result.used_2fa_method).to eq(nil)
@@ -355,11 +356,11 @@ RSpec.describe SecondFactorManager do
context "when backup codes enabled" do
it "validates codes OK" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "sets used_2fa_method to backup codes" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(
UserSecondFactor.methods[:backup_codes],
)
end
@@ -369,7 +370,7 @@ RSpec.describe SecondFactorManager do
before { user.user_second_factors.backup_codes.destroy_all }
it "returns an error" do
result = user.authenticate_second_factor(params, secure_session)
result = user.authenticate_second_factor(params, server_session)
expect(result.ok).to eq(false)
expect(result.error).to eq(I18n.t("login.not_enabled_second_factor_method"))
expect(result.used_2fa_method).to eq(nil)
@@ -387,11 +388,11 @@ RSpec.describe SecondFactorManager do
end
it "validates the security key OK" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "sets used_2fa_method to security keys" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(
UserSecondFactor.methods[:security_key],
)
end
@@ -406,11 +407,11 @@ RSpec.describe SecondFactorManager do
end
it "validates totp OK" do
expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true)
expect(user.authenticate_second_factor(params, server_session).ok).to eq(true)
end
it "sets used_2fa_method to totp" do
expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(
expect(user.authenticate_second_factor(params, server_session).used_2fa_method).to eq(
UserSecondFactor.methods[:totp],
)
end
@@ -58,7 +58,7 @@ RSpec.describe DiscourseWebauthn::AuthenticationService do
let(:credential_id) do
"mJAJ4CznTO0SuLkJbYwpgK75ao4KMNIPlU5KWM92nq39kRbXzI9mSv6GxTcsMYoiPgaouNw7b7zBiS4vsQaO6A=="
end
let(:secure_session) { SecureSession.new("tester") }
let(:server_session) { ServerSession.new("tester") }
let(:challenge) { "81d4acfbd69eafa8f02bc2ecbec5267be8c9b28c1e0ba306d52b79f0f13d" }
let(:client_data_challenge) { Base64.strict_encode64(challenge) }
let(:client_data_webauthn_type) { "webauthn.get" }
@@ -92,7 +92,7 @@ RSpec.describe DiscourseWebauthn::AuthenticationService do
end
let(:options) do
{ session: secure_session, factor_type: UserSecurityKey.factor_types[:second_factor] }
{ session: server_session, factor_type: UserSecurityKey.factor_types[:second_factor] }
end
let(:current_user) { Fabricate(:user) }
@@ -273,7 +273,7 @@ RSpec.describe DiscourseWebauthn::AuthenticationService do
describe "authenticating passkeys" do
let(:options) do
{ factor_type: UserSecurityKey.factor_types[:first_factor], session: secure_session }
{ factor_type: UserSecurityKey.factor_types[:first_factor], session: server_session }
end
##
@@ -10,13 +10,13 @@ RSpec.describe DiscourseWebauthn::ChallengeGenerator do
describe "ChallengeSession" do
describe "#commit_to_session" do
let(:user) { Fabricate(:user) }
let(:server_session) { ServerSession.new("some-prefix") }
let(:generated_session) { DiscourseWebauthn::ChallengeGenerator.generate }
it "stores the challenge in the provided session object" do
secure_session = SecureSession.new("some-prefix")
generated_session = DiscourseWebauthn::ChallengeGenerator.generate
generated_session.commit_to_session(secure_session, user)
generated_session.commit_to_session(server_session, user)
expect(secure_session["staged-webauthn-challenge-#{user&.id}"]).to eq(
expect(server_session["staged-webauthn-challenge-#{user&.id}"]).to eq(
generated_session.challenge,
)
end
@@ -17,32 +17,32 @@ RSpec.describe DiscourseWebauthn do
end
describe ".stage_challenge" do
let(:secure_session) { SecureSession.new("some-prefix") }
let(:server_session) { ServerSession.new("some-prefix") }
it "stores the challenge in the provided session object with the right expiry" do
described_class.stage_challenge(user, secure_session)
described_class.stage_challenge(user, server_session)
key = described_class.session_challenge_key(user)
expect(secure_session[key]).to be_present
expect(server_session[key]).to be_present
expect(secure_session.ttl(key)).to be_within_one_second_of(
expect(server_session.ttl(key)).to be_within_one_second_of(
DiscourseWebauthn::CHALLENGE_EXPIRY,
)
end
end
describe ".clear_challenge" do
let(:secure_session) { SecureSession.new("some-prefix") }
let(:server_session) { ServerSession.new("some-prefix") }
it "clears the challenge from the provided session object" do
described_class.stage_challenge(user, secure_session)
described_class.stage_challenge(user, server_session)
key = described_class.session_challenge_key(user)
expect(secure_session[key]).to be_present
expect(server_session[key]).to be_present
described_class.clear_challenge(user, secure_session)
described_class.clear_challenge(user, server_session)
expect(secure_session[key]).to be_nil
expect(server_session[key]).to be_nil
end
end
end
@@ -4,7 +4,7 @@ require "discourse_webauthn"
RSpec.describe DiscourseWebauthn::RegistrationService do
subject(:service) { described_class.new(current_user, params, **options) }
let(:secure_session) { SecureSession.new("tester") }
let(:server_session) { ServerSession.new("tester") }
let(:client_data_challenge) { Base64.encode64(challenge) }
let(:client_data_webauthn_type) { "webauthn.create" }
let(:client_data_origin) { "http://test.localhost" }
@@ -34,10 +34,10 @@ RSpec.describe DiscourseWebauthn::RegistrationService do
# The above attestation was generated in localhost; Discourse.current_hostname
# returns test.localhost which we do not want
let(:options) do
{ session: secure_session, factor_type: UserSecurityKey.factor_types[:second_factor] }
{ session: server_session, factor_type: UserSecurityKey.factor_types[:second_factor] }
end
let(:challenge) { DiscourseWebauthn.stage_challenge(current_user, secure_session).challenge }
let(:challenge) { DiscourseWebauthn.stage_challenge(current_user, server_session).challenge }
let(:current_user) { Fabricate(:user) }
@@ -178,7 +178,7 @@ RSpec.describe DiscourseWebauthn::RegistrationService do
describe "registering a second factor key as first factor" do
let(:options) do
{ factor_type: UserSecurityKey.factor_types[:first_factor], session: secure_session }
{ factor_type: UserSecurityKey.factor_types[:first_factor], session: server_session }
end
it "does not work since second-factor key does not have the user verification flag" do
@@ -191,7 +191,7 @@ RSpec.describe DiscourseWebauthn::RegistrationService do
describe "registering a passkey" do
let(:options) do
{ factor_type: UserSecurityKey.factor_types[:first_factor], session: secure_session }
{ factor_type: UserSecurityKey.factor_types[:first_factor], session: server_session }
end
##
-27
View File
@@ -1,27 +0,0 @@
# frozen_string_literal: true
RSpec.describe SecureSession do
it "operates correctly" do
s = SecureSession.new("abc")
s["hello"] = "world"
s["foo"] = "bar"
expect(s["hello"]).to eq("world")
expect(s["foo"]).to eq("bar")
s["hello"] = nil
expect(s["hello"]).to eq(nil)
end
it "can override expiry" do
s = SecureSession.new("abc")
key = SecureRandom.hex
s.set(key, "test2", expires: 5.minutes)
expect(s.ttl(key)).to be_within(1.second).of(5.minutes)
key = SecureRandom.hex
s.set(key, "test2")
expect(s.ttl(key)).to be_within(1.second).of(SecureSession.expiry)
end
end
+26
View File
@@ -0,0 +1,26 @@
# frozen_string_literal: true
RSpec.describe ServerSession do
subject(:session) { described_class.new("abc") }
it "operates correctly" do
session["hello"] = "world"
session["foo"] = "bar"
expect(session["hello"]).to eq("world")
expect(session["foo"]).to eq("bar")
session["hello"] = nil
expect(session["hello"]).to be_nil
end
it "can override expiry" do
key = SecureRandom.hex
session.set(key, "test2", expires: 5.minutes)
expect(session.ttl(key)).to be_within(1.second).of(5.minutes)
key = SecureRandom.hex
session.set(key, "test2")
expect(session.ttl(key)).to be_within(1.second).of(described_class.expiry)
end
end
+16 -16
View File
@@ -39,7 +39,7 @@ RSpec.describe DiscourseConnect do
end
def new_discourse_sso
DiscourseConnect.new(secure_session: secure_session)
DiscourseConnect.new(secure_session: server_session)
end
def test_parsed(parsed, sso)
@@ -77,7 +77,7 @@ RSpec.describe DiscourseConnect do
end
let(:ip_address) { "127.0.0.1" }
let(:secure_session) { SecureSession.new("abc") }
let(:server_session) { ServerSession.new("abc") }
it "bans bad external id" do
sso = new_discourse_sso
@@ -756,13 +756,13 @@ RSpec.describe DiscourseConnect do
end
it "validates nonce" do
_, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
_, payload = DiscourseConnect.generate_url(secure_session: server_session).split("?")
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
sso = DiscourseConnect.parse(payload, secure_session: server_session)
expect(sso.nonce_valid?).to eq true
other_session_sso =
DiscourseConnect.parse(payload, secure_session: SecureSession.new("differentsession"))
DiscourseConnect.parse(payload, secure_session: ServerSession.new("differentsession"))
expect(other_session_sso.nonce_valid?).to eq false
sso.expire_nonce!
@@ -772,13 +772,13 @@ RSpec.describe DiscourseConnect do
it "allows disabling CSRF protection" do
SiteSetting.discourse_connect_csrf_protection = false
_, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
_, payload = DiscourseConnect.generate_url(secure_session: server_session).split("?")
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
sso = DiscourseConnect.parse(payload, secure_session: server_session)
expect(sso.nonce_valid?).to eq true
other_session_sso =
DiscourseConnect.parse(payload, secure_session: SecureSession.new("differentsession"))
DiscourseConnect.parse(payload, secure_session: ServerSession.new("differentsession"))
expect(other_session_sso.nonce_valid?).to eq true
sso.expire_nonce!
@@ -787,18 +787,18 @@ RSpec.describe DiscourseConnect do
end
it "generates a correct sso url" do
url, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
url, payload = DiscourseConnect.generate_url(secure_session: server_session).split("?")
expect(url).to eq discourse_connect_url
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
sso = DiscourseConnect.parse(payload, secure_session: server_session)
expect(sso.nonce).to_not be_nil
end
describe "nonce error" do
it "generates correct error message when nonce has already been used" do
_, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
_, payload = DiscourseConnect.generate_url(secure_session: server_session).split("?")
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
sso = DiscourseConnect.parse(payload, secure_session: server_session)
expect(sso.nonce_valid?).to eq true
sso.expire_nonce!
@@ -806,9 +806,9 @@ RSpec.describe DiscourseConnect do
end
it "generates correct error message when nonce is expired" do
_, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
_, payload = DiscourseConnect.generate_url(secure_session: server_session).split("?")
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
sso = DiscourseConnect.parse(payload, secure_session: server_session)
expect(sso.nonce_valid?).to eq true
Discourse.cache.delete(sso.used_nonce_key)
@@ -819,9 +819,9 @@ RSpec.describe DiscourseConnect do
it "generates correct error message when nonce is expired, and csrf protection disabled" do
SiteSetting.discourse_connect_csrf_protection = false
_, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
_, payload = DiscourseConnect.generate_url(secure_session: server_session).split("?")
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
sso = DiscourseConnect.parse(payload, secure_session: server_session)
expect(sso.nonce_valid?).to eq true
Discourse.cache.delete(sso.used_nonce_key)
+1 -3
View File
@@ -119,10 +119,8 @@ RSpec.describe SessionController do
expect(response_body_parsed["allowed_credential_ids"]).to eq(
[user_security_key.credential_id],
)
secure_session = SecureSession.new(session["secure_session_id"])
expect(response_body_parsed["challenge"]).to eq(
DiscourseWebauthn.challenge(user, secure_session),
DiscourseWebauthn.challenge(user, request.server_session),
)
expect(DiscourseWebauthn.rp_id).to eq("localhost")
end
+7 -8
View File
@@ -22,6 +22,8 @@ RSpec.describe UsersController do
before { SiteSetting.hide_email_address_taken = false }
describe "#full account registration flow" do
let(:server_session) { request.server_session }
it "will correctly handle honeypot and challenge" do
get "/session/hp.json"
expect(response.status).to eq(200)
@@ -37,10 +39,8 @@ RSpec.describe UsersController do
password: SecureRandom.hex,
}
secure_session = SecureSession.new(session["secure_session_id"])
expect(secure_session[UsersController::HONEYPOT_KEY]).to eq(json["value"])
expect(secure_session[UsersController::CHALLENGE_KEY]).to eq(json["challenge"])
expect(server_session[UsersController::HONEYPOT_KEY]).to eq(json["value"])
expect(server_session[UsersController::CHALLENGE_KEY]).to eq(json["challenge"])
post "/u.json", params: params
@@ -50,8 +50,8 @@ RSpec.describe UsersController do
expect(jane.email).to eq("jane@jane.com")
expect(secure_session[UsersController::HONEYPOT_KEY]).to eq(nil)
expect(secure_session[UsersController::CHALLENGE_KEY]).to eq(nil)
expect(server_session[UsersController::HONEYPOT_KEY]).to eq(nil)
expect(server_session[UsersController::CHALLENGE_KEY]).to eq(nil)
end
end
@@ -627,8 +627,7 @@ RSpec.describe UsersController do
end
it "stages a webauthn challenge for the user" do
secure_session = SecureSession.new(session["secure_session_id"])
expect(DiscourseWebauthn.challenge(user1, secure_session)).not_to eq(nil)
expect(DiscourseWebauthn.challenge(user1, request.server_session)).not_to be_blank
end
it "changes password with valid security key challenge and authentication" do
+2 -2
View File
@@ -38,7 +38,7 @@ module IntegrationHelpers
def read_secure_session
id =
begin
session[:secure_session_id]
session[:server_session_id]
rescue NoMethodError
nil
end
@@ -46,7 +46,7 @@ module IntegrationHelpers
# This route will init the secure_session for us
get "/session/hp.json" if id.nil?
SecureSession.new(session[:secure_session_id])
ServerSession.new(session[:server_session_id])
end
def write_secure_session(key, value)