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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +217 -2
- data/README.md +16 -2
- data/app/controllers/mcp/auth/oauth_controller.rb +264 -56
- data/app/controllers/mcp/auth/well_known_controller.rb +23 -8
- data/app/models/mcp/auth/access_token.rb +5 -0
- data/app/models/mcp/auth/authorization_code.rb +4 -0
- data/app/models/mcp/auth/oauth_client.rb +154 -4
- data/app/models/mcp/auth/refresh_token.rb +12 -1
- data/app/views/mcp/auth/consent.html.erb +13 -0
- data/lib/generators/mcp/auth/hash_secrets_generator.rb +48 -0
- data/lib/generators/mcp/auth/install_generator.rb +13 -1
- data/lib/generators/mcp/auth/templates/add_mcp_auth_confidential_client_and_reuse.rb.erb +39 -0
- data/lib/generators/mcp/auth/templates/hash_mcp_auth_secrets_at_rest.rb.erb +72 -0
- data/lib/generators/mcp/auth/templates/initializer.rb +67 -4
- data/lib/generators/mcp/auth/templates/views/consent.html.erb +13 -0
- data/lib/generators/mcp/auth/upgrade_generator.rb +60 -0
- data/lib/mcp/auth/engine.rb +35 -1
- data/lib/mcp/auth/protected_resource.rb +29 -6
- data/lib/mcp/auth/schema_guard.rb +67 -0
- data/lib/mcp/auth/scope_registry.rb +12 -0
- data/lib/mcp/auth/secret_hashing.rb +83 -0
- data/lib/mcp/auth/services/authorization_service.rb +18 -8
- data/lib/mcp/auth/services/token_service.rb +144 -23
- data/lib/mcp/auth/version.rb +1 -1
- data/lib/mcp/auth.rb +51 -4
- data/lib/tasks/mcp_auth_tasks.rake +40 -6
- metadata +18 -1
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::
|
|
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 =
|
|
59
|
-
|
|
60
|
-
|
|
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 =
|
|
81
|
-
|
|
82
|
-
|
|
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.
|
|
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
|