mcp-auth 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/mcp/auth.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'mcp/auth/version'
4
4
  require 'mcp/auth/engine'
5
+ require 'mcp/auth/secret_hashing'
5
6
  require 'mcp/auth/services/token_service'
6
7
  require 'mcp/auth/services/authorization_service'
7
8
 
@@ -40,7 +41,14 @@ module Mcp
40
41
  :token_signing_private_key,
41
42
  :token_signing_public_key,
42
43
  :token_signing_additional_public_keys,
43
- :token_signing_kid
44
+ :token_signing_kid,
45
+ :secret_dual_read,
46
+ :refresh_token_reuse_grace_period,
47
+ :mcp_server_url,
48
+ :allowed_redirect_uri_patterns,
49
+ :allow_loopback_redirects,
50
+ :verified_redirect_hosts,
51
+ :strict_scope_validation
44
52
 
45
53
  # token_signing_algorithm has a validating writer defined below, so only
46
54
  # the reader is generated here.
@@ -58,8 +66,22 @@ module Mcp
58
66
  @consent_view_path = 'mcp/auth/consent'
59
67
  @use_custom_consent_view = false
60
68
  @mcp_server_path = '/mcp'
69
+ # Public origin of the MCP resource server (e.g. 'https://api.example.com').
70
+ # The token audience, protected-resource metadata and 401 challenge are
71
+ # built from it. Optional: defaults to the request origin. Set it to pin
72
+ # these against a forged Host header, and whenever authorization_server_url
73
+ # points at a different host.
74
+ @mcp_server_url = nil
61
75
  @mcp_docs_url = nil
62
76
  @validate_scope_for_user = nil
77
+ # Refresh-token rotation grace period (seconds). A rotated (revoked)
78
+ # refresh token replayed WITHIN this window of its rotation is treated as
79
+ # a benign client race/retry — rejected softly, WITHOUT revoking the
80
+ # family — because real MCP clients often fire several refreshes at once
81
+ # when the access token expires. A replay after the window is treated as
82
+ # genuine reuse/theft and revokes the whole family. Set to 0 to disable
83
+ # the grace and revoke on any replay.
84
+ @refresh_token_reuse_grace_period = 10
63
85
  # CP-9255 batch 2: JWT signing.
64
86
  # Default HS256 keeps existing setups working (shared oauth_secret).
65
87
  # Set algorithm to 'RS256' or 'ES256' and provide PEM-encoded keys
@@ -71,6 +93,30 @@ module Mcp
71
93
  # for verification and published in the JWKS during key rotation.
72
94
  @token_signing_additional_public_keys = []
73
95
  @token_signing_kid = nil
96
+ # Transitional dual-read for the "hash secrets at rest" migration. While
97
+ # true (the default), a presented secret/token/code is matched against
98
+ # BOTH its digest and any legacy plaintext row not yet backfilled — this
99
+ # is what makes the upgrade safe under rolling deploys and safe to roll
100
+ # back. Set to false once every row is hashed (the backfill migration has
101
+ # run and no old code remains) to reject plaintext-form matches.
102
+ @secret_dual_read = true
103
+ # Dynamic Client Registration redirect-URI policy. Registration is open
104
+ # (RFC 7591), so a rogue client could register an attacker-controlled
105
+ # redirect_uri and phish a user into approving it on the genuine consent
106
+ # page. Plain http is ALWAYS rejected except for loopback hosts. Set
107
+ # allowed_redirect_uri_patterns (Strings, matched exactly, or Regexps)
108
+ # to additionally restrict registration to known clients; nil keeps any
109
+ # https / native-scheme URI. Loopback (localhost, 127.0.0.1, [::1], any
110
+ # port; RFC 8252 §7.3) is allowed unless allow_loopback_redirects is false.
111
+ @allowed_redirect_uri_patterns = nil
112
+ @allow_loopback_redirects = true
113
+ # Redirect hosts shown as "verified" on the consent screen (exact host or
114
+ # '*.example.com'); any other host is flagged as unverified.
115
+ @verified_redirect_hosts = []
116
+ # true: unknown scopes are rejected (invalid_client_metadata at
117
+ # registration, invalid_scope at /oauth/authorize). false: silently
118
+ # dropped, the pre-0.6.0 behavior.
119
+ @strict_scope_validation = true
74
120
  end
75
121
 
76
122
  def token_signing_algorithm=(value)
@@ -139,8 +185,9 @@ module Mcp
139
185
  end
140
186
  end
141
187
 
142
- # Loaded after the module body so they can reference Mcp::Auth::Configuration
143
- # and Mcp::Auth::ControllerHelpers defined above. The services are already
144
- # required at the top of this file.
188
+ # Loaded after the module body so they can reference Mcp::Auth::Configuration,
189
+ # Mcp::Auth::ControllerHelpers, and Mcp::Auth::Error defined above. The services
190
+ # are already required at the top of this file.
145
191
  require 'mcp/auth/scope_registry'
146
192
  require 'mcp/auth/protected_resource'
193
+ require 'mcp/auth/schema_guard'
@@ -17,6 +17,32 @@ namespace :mcp_auth do
17
17
  puts "Cleanup complete!"
18
18
  end
19
19
 
20
+ desc "Check that the database schema matches the installed mcp-auth version"
21
+ task doctor: :environment do
22
+ missing = Mcp::Auth::SchemaGuard.missing_columns
23
+ secret_problem = Mcp::Auth::Services::TokenService.oauth_secret_problem
24
+ healthy = true
25
+
26
+ if missing.empty?
27
+ puts "mcp-auth #{Mcp::Auth::VERSION}: database schema is up to date."
28
+ else
29
+ healthy = false
30
+ warn "mcp-auth #{Mcp::Auth::VERSION}: PENDING MIGRATION"
31
+ warn " Missing: #{missing.join(', ')}"
32
+ warn ' Run: rails g mcp:auth:upgrade && rails db:migrate'
33
+ end
34
+
35
+ if secret_problem
36
+ # A hard error at token-signing time outside development/test.
37
+ healthy = false unless Rails.env.development? || Rails.env.test?
38
+ warn "mcp-auth: signing secret: #{secret_problem}"
39
+ else
40
+ puts 'mcp-auth: signing configuration OK.'
41
+ end
42
+
43
+ exit 1 unless healthy
44
+ end
45
+
20
46
  desc "Show MCP Auth statistics"
21
47
  task stats: :environment do
22
48
  puts "\nMCP Auth Statistics"
@@ -55,9 +81,14 @@ namespace :mcp_auth do
55
81
 
56
82
  puts "Revoking all tokens for client: #{client_id}"
57
83
 
58
- auth_codes = Mcp::Auth::AuthorizationCode.where(client_id: client_id).delete_all
59
- access_tokens = Mcp::Auth::AccessToken.where(client_id: client_id).delete_all
60
- refresh_tokens = Mcp::Auth::RefreshToken.where(client_id: client_id).delete_all
84
+ auth_codes = access_tokens = refresh_tokens = 0
85
+ # Wrap the three deletes in a transaction so a mid-way failure can't leave a
86
+ # client partially revoked (tokens gone but codes still live, or similar).
87
+ ActiveRecord::Base.transaction do
88
+ auth_codes = Mcp::Auth::AuthorizationCode.where(client_id: client_id).delete_all
89
+ access_tokens = Mcp::Auth::AccessToken.where(client_id: client_id).delete_all
90
+ refresh_tokens = Mcp::Auth::RefreshToken.where(client_id: client_id).delete_all
91
+ end
61
92
 
62
93
  puts " - Removed #{auth_codes} authorization codes"
63
94
  puts " - Removed #{access_tokens} access tokens"
@@ -77,9 +108,12 @@ namespace :mcp_auth do
77
108
 
78
109
  puts "Revoking all tokens for user: #{user_id}"
79
110
 
80
- auth_codes = Mcp::Auth::AuthorizationCode.where(user_id: user_id).delete_all
81
- access_tokens = Mcp::Auth::AccessToken.where(user_id: user_id).delete_all
82
- refresh_tokens = Mcp::Auth::RefreshToken.where(user_id: user_id).delete_all
111
+ auth_codes = access_tokens = refresh_tokens = 0
112
+ ActiveRecord::Base.transaction do
113
+ auth_codes = Mcp::Auth::AuthorizationCode.where(user_id: user_id).delete_all
114
+ access_tokens = Mcp::Auth::AccessToken.where(user_id: user_id).delete_all
115
+ refresh_tokens = Mcp::Auth::RefreshToken.where(user_id: user_id).delete_all
116
+ end
83
117
 
84
118
  puts " - Removed #{auth_codes} authorization codes"
85
119
  puts " - Removed #{access_tokens} access tokens"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mcp-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Serhii Borozenets
@@ -211,18 +211,24 @@ files:
211
211
  - app/models/mcp/auth/refresh_token.rb
212
212
  - app/views/mcp/auth/consent.html.erb
213
213
  - config/routes.rb
214
+ - lib/generators/mcp/auth/hash_secrets_generator.rb
214
215
  - lib/generators/mcp/auth/install_generator.rb
215
216
  - lib/generators/mcp/auth/templates/README
217
+ - lib/generators/mcp/auth/templates/add_mcp_auth_confidential_client_and_reuse.rb.erb
216
218
  - lib/generators/mcp/auth/templates/create_access_tokens.rb.erb
217
219
  - lib/generators/mcp/auth/templates/create_authorization_codes.rb.erb
218
220
  - lib/generators/mcp/auth/templates/create_oauth_clients.rb.erb
219
221
  - lib/generators/mcp/auth/templates/create_refresh_tokens.rb.erb
222
+ - lib/generators/mcp/auth/templates/hash_mcp_auth_secrets_at_rest.rb.erb
220
223
  - lib/generators/mcp/auth/templates/initializer.rb
221
224
  - lib/generators/mcp/auth/templates/views/consent.html.erb
225
+ - lib/generators/mcp/auth/upgrade_generator.rb
222
226
  - lib/mcp/auth.rb
223
227
  - lib/mcp/auth/engine.rb
224
228
  - lib/mcp/auth/protected_resource.rb
229
+ - lib/mcp/auth/schema_guard.rb
225
230
  - lib/mcp/auth/scope_registry.rb
231
+ - lib/mcp/auth/secret_hashing.rb
226
232
  - lib/mcp/auth/services/authorization_service.rb
227
233
  - lib/mcp/auth/services/token_service.rb
228
234
  - lib/mcp/auth/version.rb
@@ -235,6 +241,17 @@ metadata:
235
241
  changelog_uri: https://github.com/SerhiiBorozenets/mcp-auth/blob/main/CHANGELOG.md
236
242
  bug_tracker_uri: https://github.com/SerhiiBorozenets/mcp-auth/issues
237
243
  rubygems_mfa_required: 'true'
244
+ post_install_message: |
245
+ mcp-auth installed. Run pending migrations before starting your app:
246
+
247
+ rails generate mcp:auth:install # first-time install
248
+ rails generate mcp:auth:upgrade # upgrading an existing install
249
+ rails db:migrate
250
+
251
+ Upgrading from <= 0.5.0? Read the 0.6.0 CHANGELOG "Upgrade" section first:
252
+ secrets are hashed in a separate, later step (mcp:auth:hash_secrets).
253
+
254
+ Verify the schema any time with: bin/rails mcp_auth:doctor
238
255
  rdoc_options: []
239
256
  require_paths:
240
257
  - lib