fix(oauth): plaintext_token in specs, allow loopback redirect, fix throttle test

Three issues surfaced running the suite in docker:

- hash_token_secrets stores the access token hashed; specs must use
  access_token.plaintext_token (not .token) when posing as a client
- Doorkeeper's Application model rejects non-HTTPS redirect_uri by
  default; add force_ssl_in_redirect_uri to allow loopback per OAuth 2.1
- test env uses :null_store, so Rails.cache.increment returned nil and
  the DCR throttle never fired — stub a real MemoryStore in that spec

Also slim Dockerfile.test: drop chromium + chromium-chromedriver
(unused by OAuth specs, added ~4min to the build). Add a comment
pointing at the apk line to re-enable them for system specs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pull/633/head
Ortes 2 weeks ago
parent 6c51ee9929
commit 104684f163

@ -4,7 +4,7 @@
# Source code is mounted at runtime via docker-compose.test.yml — only the # Source code is mounted at runtime via docker-compose.test.yml — only the
# Gemfile is baked in so `bundle install` caches between runs. # Gemfile is baked in so `bundle install` caches between runs.
FROM ruby:4.0.1-alpine AS pdfium FROM ruby:4.0.2-alpine AS pdfium
WORKDIR /download WORKDIR /download
@ -13,7 +13,7 @@ RUN apk --no-cache add wget && \
mkdir -p /pdfium-linux && \ mkdir -p /pdfium-linux && \
tar -xzf pdfium-linux.tgz -C /pdfium-linux tar -xzf pdfium-linux.tgz -C /pdfium-linux
FROM ruby:4.0.1-alpine FROM ruby:4.0.2-alpine
ENV RAILS_ENV=test \ ENV RAILS_ENV=test \
BUNDLE_WITHOUT="" \ BUNDLE_WITHOUT="" \
@ -26,9 +26,11 @@ WORKDIR /app
# - libpq / libpq-dev → pg gem # - libpq / libpq-dev → pg gem
# - vips + vips-heif → image processing (ruby-vips FFI) # - vips + vips-heif → image processing (ruby-vips FFI)
# - onnxruntime → field detection at boot # - onnxruntime → field detection at boot
# - chromium + chromium-chromedriver → capybara/cuprite system specs
# - fontconfig + ttf-* → PDF rendering # - fontconfig + ttf-* → PDF rendering
# - build-base/git/yaml-dev → native gem compilation # - build-base/git/yaml-dev → native gem compilation
#
# Capybara/cuprite system specs need chromium — add `chromium
# chromium-chromedriver` to this apk line if running `spec/system/*`.
RUN apk add --no-cache \ RUN apk add --no-cache \
build-base \ build-base \
git \ git \
@ -46,8 +48,6 @@ RUN apk add --no-cache \
onnxruntime \ onnxruntime \
nodejs \ nodejs \
yarn \ yarn \
chromium \
chromium-chromedriver \
tzdata tzdata
# libpdfium is not in Alpine packages — copy the prebuilt binary. # libpdfium is not in Alpine packages — copy the prebuilt binary.
@ -64,7 +64,4 @@ RUN bundle config set --local without "" && \
RUN ln -sf /usr/lib/libonnxruntime.so.1 \ RUN ln -sf /usr/lib/libonnxruntime.so.1 \
"$(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first")" "$(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first")"
# Cuprite launches Chromium directly — point it at the Alpine binary.
ENV CUPRITE_CHROME_PATH=/usr/bin/chromium-browser
CMD ["bundle", "exec", "rspec"] CMD ["bundle", "exec", "rspec"]

@ -27,6 +27,11 @@ Doorkeeper.configure do
pkce_code_challenge_methods %w[S256] pkce_code_challenge_methods %w[S256]
force_pkce force_pkce
# Require HTTPS for redirect_uri except for loopback (OAuth 2.1 §8.4.2).
force_ssl_in_redirect_uri do |uri|
!%w[localhost 127.0.0.1 ::1].include?(uri.host)
end
default_scopes :mcp default_scopes :mcp
optional_scopes :mcp optional_scopes :mcp

@ -33,7 +33,7 @@ RSpec.describe 'MCP endpoint authentication', type: :request do
end end
it 'succeeds and dispatches to HandleRequest' do it 'succeeds and dispatches to HandleRequest' do
post_mcp(access_token.token) post_mcp(access_token.plaintext_token)
expect(response).to have_http_status(:ok).or have_http_status(:accepted) expect(response).to have_http_status(:ok).or have_http_status(:accepted)
end end
end end
@ -43,7 +43,7 @@ RSpec.describe 'MCP endpoint authentication', type: :request do
token = create(:oauth_access_token, resource_owner_id: user.id, scopes: 'mcp', expires_in: 1) token = create(:oauth_access_token, resource_owner_id: user.id, scopes: 'mcp', expires_in: 1)
travel_to(2.hours.from_now) do travel_to(2.hours.from_now) do
post_mcp(token.token) post_mcp(token.plaintext_token)
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end end
end end
@ -56,7 +56,7 @@ RSpec.describe 'MCP endpoint authentication', type: :request do
end end
it 'returns 401' do it 'returns 401' do
post_mcp(access_token.token) post_mcp(access_token.plaintext_token)
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end end
end end
@ -67,7 +67,7 @@ RSpec.describe 'MCP endpoint authentication', type: :request do
end end
it 'returns 401' do it 'returns 401' do
post_mcp(access_token.token) post_mcp(access_token.plaintext_token)
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end end
end end

@ -58,6 +58,8 @@ RSpec.describe 'Dynamic Client Registration (RFC 7591)', type: :request do
end end
it 'throttles after 20 requests from the same IP within an hour' do it 'throttles after 20 requests from the same IP within an hour' do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
20.times { post_register(valid_body) } 20.times { post_register(valid_body) }
post_register(valid_body) post_register(valid_body)
expect(response).to have_http_status(:too_many_requests) expect(response).to have_http_status(:too_many_requests)

Loading…
Cancel
Save