mirror of
https://github.com/discourse/discourse.git
synced 2025-07-13 04:39:50 +08:00

Adds a Discourse ID authenticator. Not available for use in production just yet, but soon communities will be able to use this service to let users authenticate using a central Discourse ID account. Includes a support for a `/revoke` action, allowing users to log out of multiple client instances from a central auth service. Internal ticket: t/155397 --------- Co-authored-by: Loïc Guitaut <loic@discourse.org>
136 lines
4.4 KiB
Ruby
136 lines
4.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Users::DiscourseIdController do
|
|
let(:client_id) { SiteSetting.discourse_id_client_id }
|
|
let(:hashed_secret) { Digest::SHA256.hexdigest(SiteSetting.discourse_id_client_secret) }
|
|
let(:identifier) { SecureRandom.hex }
|
|
let(:provider_name) { "discourse_id" }
|
|
|
|
let!(:user) { Fabricate(:user) }
|
|
let!(:user_associated_account) do
|
|
Fabricate(:user_associated_account, user:, provider_name:, provider_uid: identifier)
|
|
end
|
|
|
|
before do
|
|
SiteSetting.enable_discourse_id = true
|
|
SiteSetting.discourse_id_client_id = SecureRandom.hex
|
|
SiteSetting.discourse_id_client_secret = SecureRandom.hex
|
|
end
|
|
|
|
describe "#revoke" do
|
|
context "with valid parameters" do
|
|
it "revokes all auth tokens for the user" do
|
|
UserAuthToken.generate!(user_id: user.id)
|
|
UserAuthToken.generate!(user_id: user.id)
|
|
|
|
expect(UserAuthToken.where(user_id: user.id).count).to eq(2)
|
|
|
|
timestamp = Time.now.to_i
|
|
signature =
|
|
OpenSSL::HMAC.hexdigest(
|
|
"sha256",
|
|
hashed_secret,
|
|
"#{client_id}:#{identifier}:#{timestamp}",
|
|
)
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["success"]).to eq(true)
|
|
|
|
expect(UserAuthToken.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "with invalid parameters" do
|
|
it "returns 400 when signature is invalid" do
|
|
timestamp = Time.now.to_i
|
|
signature = SecureRandom.hex
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
|
|
expect(response.status).to eq(400)
|
|
expect(response.parsed_body["error"]).to match(/Signature is invalid/)
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it "returns 400 when timestamp is too old" do
|
|
timestamp = Time.now.to_i - 6.minutes.to_i
|
|
signature =
|
|
OpenSSL::HMAC.hexdigest(
|
|
"sha256",
|
|
hashed_secret,
|
|
"#{client_id}:#{identifier}:#{timestamp}",
|
|
)
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
|
|
expect(response.status).to eq(400)
|
|
expect(response.parsed_body["error"]).to match(/Timestamp is expired/)
|
|
end
|
|
|
|
it "returns 400 when user_id is not found" do
|
|
identifier = "non_existent_user_id"
|
|
timestamp = Time.now.to_i
|
|
signature =
|
|
OpenSSL::HMAC.hexdigest(
|
|
"sha256",
|
|
hashed_secret,
|
|
"#{client_id}:#{identifier}:#{timestamp}",
|
|
)
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
|
|
expect(response.status).to eq(400)
|
|
expect(response.parsed_body["error"]).to eq("Invalid request")
|
|
end
|
|
|
|
it "returns 400 when client_id or client_secret is blank" do
|
|
SiteSetting.discourse_id_client_id = ""
|
|
|
|
timestamp = Time.now.to_i
|
|
signature =
|
|
OpenSSL::HMAC.hexdigest(
|
|
"sha256",
|
|
hashed_secret,
|
|
"#{client_id}:#{identifier}:#{timestamp}",
|
|
)
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
|
|
expect(response.status).to eq(400)
|
|
expect(response.parsed_body["error"]).to eq("Invalid request")
|
|
|
|
SiteSetting.discourse_id_client_id = SecureRandom.hex
|
|
SiteSetting.discourse_id_client_secret = ""
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
|
|
expect(response.status).to eq(400)
|
|
expect(response.parsed_body["error"]).to eq("Invalid request")
|
|
end
|
|
end
|
|
|
|
context "with rate limiting" do
|
|
before { RateLimiter.enable }
|
|
|
|
it "rate limits after 5 requests per minute for the same user_id" do
|
|
identifier = "non_existent_user_id"
|
|
timestamp = Time.now.to_i
|
|
signature = SecureRandom.hex
|
|
|
|
5.times do
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
|
expect(response.status).to eq(429)
|
|
end
|
|
end
|
|
end
|
|
end
|