agentadmit 1.3.0 → 1.4.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/README.md +21 -0
- data/lib/agentadmit/introspection_client.rb +21 -2
- data/lib/agentadmit/middleware.rb +2 -0
- data/lib/agentadmit/scope_enforcement.rb +23 -0
- data/lib/agentadmit/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc3085f5469fd5380afb22df98cf1517e1f74df3a23cf7ab07d7f6c490cd9feb
|
|
4
|
+
data.tar.gz: fb0142a79357486644fff3e68903194b998cf5b41c1f140a8bda8f7a2f0c2909
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e54b1b2f7c357b546388d09a9accf94c29c98964ade0d5888e87962d3d30219486cf30b932234d503e46d6ea80e4a6348a165a6172a09b4d182390709c605713
|
|
7
|
+
data.tar.gz: 3d237b464785de4d0a8a081d992dd7313c77c84a3bdac92f0a3a9dc41a2909137cfec0415d7eb67dd4408c1eb772d24ff514e996184ad0d580b9f047fcd0a59d
|
data/README.md
CHANGED
|
@@ -96,6 +96,27 @@ head :forbidden unless verdict["granted"]
|
|
|
96
96
|
|
|
97
97
|
Consent is orthogonal to revocation: a denied verdict means your app returns its own 403; the connection and token stay valid so the user can flip consent back on without re-connecting. Write switches through `PUT /api/v1/consent/settings` from your backend; export the audit trail with `GET /api/v1/consent/export` (every plan).
|
|
98
98
|
|
|
99
|
+
### Presence verification
|
|
100
|
+
|
|
101
|
+
AgentAdmit can attest that the human who authorized a connection completed a WebAuthn presence ceremony on the consent page. The verify result carries the fact:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
result = AgentAdmit::IntrospectionClient.new.verify(token)
|
|
105
|
+
result.presence_verified? # true only when the ceremony completed
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For sensitive actions, enforce it in your controllers the same way you enforce scopes:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
class TransfersController < ApplicationController
|
|
112
|
+
include AgentAdmit::ScopeEnforcement
|
|
113
|
+
|
|
114
|
+
before_action -> { require_presence! }, only: [:create]
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`require_presence!` fails closed: agents whose connection was minted without a completed ceremony get a 403 `presence_required`, and so do connections from servers that predate the feature. `presence_verified?` returns true only on an explicit boolean `verified: true`; absent or malformed presence data reads as not verified.
|
|
119
|
+
|
|
99
120
|
## Rate Limiting
|
|
100
121
|
|
|
101
122
|
The AgentAdmit introspection endpoint enforces rate limits. The Ruby SDK handles HTTP 429 responses **automatically** with exponential backoff and jitter -- no changes needed in your middleware code.
|
|
@@ -16,7 +16,8 @@ module AgentAdmit
|
|
|
16
16
|
MAX_RETRY_BUDGET_MS = 120_000
|
|
17
17
|
|
|
18
18
|
IntrospectionResult = Struct.new(:user_id, :connection_id, :scopes, :agent_label,
|
|
19
|
-
:sub, :role, :app_id, :jti, :exp, :consent,
|
|
19
|
+
:sub, :role, :app_id, :jti, :exp, :consent,
|
|
20
|
+
:presence, keyword_init: true) do
|
|
20
21
|
def has_scope?(scope)
|
|
21
22
|
scopes.include?(scope)
|
|
22
23
|
end
|
|
@@ -34,6 +35,16 @@ module AgentAdmit
|
|
|
34
35
|
return true if consent.nil?
|
|
35
36
|
consent["granted"] == true
|
|
36
37
|
end
|
|
38
|
+
|
|
39
|
+
# Human-presence fact from the WebAuthn step-up (additive; may be nil).
|
|
40
|
+
# True ONLY when the connection was authorized by a human who completed
|
|
41
|
+
# a presence ceremony on the consent page: verified must be the boolean
|
|
42
|
+
# true. Unlike consent, absence fails closed -- older servers never send
|
|
43
|
+
# the block, and connections minted without a ceremony carry
|
|
44
|
+
# verified: false, so nil/false/malformed all read as not verified.
|
|
45
|
+
def presence_verified?
|
|
46
|
+
presence.is_a?(Hash) && presence["verified"] == true
|
|
47
|
+
end
|
|
37
48
|
end
|
|
38
49
|
|
|
39
50
|
def initialize(config = nil)
|
|
@@ -157,6 +168,13 @@ module AgentAdmit
|
|
|
157
168
|
consent = data["consent"]
|
|
158
169
|
consent = nil unless consent.is_a?(Hash)
|
|
159
170
|
|
|
171
|
+
# Presence rides along when the platform returns it. Same strictness
|
|
172
|
+
# as active: verified must be the boolean true or false, never coerced.
|
|
173
|
+
# Unlike consent, a malformed block is dropped -- presence_verified?
|
|
174
|
+
# fails closed on nil, so dropping cannot fail open.
|
|
175
|
+
presence = data["presence"]
|
|
176
|
+
presence = nil unless presence.is_a?(Hash) && [true, false].include?(presence["verified"])
|
|
177
|
+
|
|
160
178
|
return IntrospectionResult.new(
|
|
161
179
|
user_id: data["user_id"],
|
|
162
180
|
connection_id: data["connection_id"],
|
|
@@ -167,7 +185,8 @@ module AgentAdmit
|
|
|
167
185
|
app_id: data["app_id"],
|
|
168
186
|
jti: data["jti"],
|
|
169
187
|
exp: data["exp"],
|
|
170
|
-
consent: consent
|
|
188
|
+
consent: consent,
|
|
189
|
+
presence: presence
|
|
171
190
|
)
|
|
172
191
|
end
|
|
173
192
|
|
|
@@ -11,6 +11,7 @@ module AgentAdmit
|
|
|
11
11
|
# env['agentadmit.scopes'] -- granted scopes array
|
|
12
12
|
# env['agentadmit.connection_id'] -- connection identifier
|
|
13
13
|
# env['agentadmit.agent_label'] -- agent display name
|
|
14
|
+
# env['agentadmit.presence'] -- human-presence block (Hash) or nil
|
|
14
15
|
#
|
|
15
16
|
class Middleware
|
|
16
17
|
# RFC 7235: the auth-scheme token is case-insensitive.
|
|
@@ -37,6 +38,7 @@ module AgentAdmit
|
|
|
37
38
|
env["agentadmit.scopes"] = result.scopes
|
|
38
39
|
env["agentadmit.connection_id"] = result.connection_id
|
|
39
40
|
env["agentadmit.agent_label"] = result.agent_label
|
|
41
|
+
env["agentadmit.presence"] = result.presence
|
|
40
42
|
rescue InvalidTokenError => e
|
|
41
43
|
return [401, { "Content-Type" => "application/json" },
|
|
42
44
|
[{ error: "invalid_token", error_description: e.message }.to_json]]
|
|
@@ -10,6 +10,7 @@ module AgentAdmit
|
|
|
10
10
|
#
|
|
11
11
|
# before_action -> { require_scope!("read:orders") }, only: [:index, :show]
|
|
12
12
|
# before_action -> { require_scope_if_agent!("create:orders") }, only: [:create]
|
|
13
|
+
# before_action -> { require_presence! }, only: [:destroy]
|
|
13
14
|
# end
|
|
14
15
|
#
|
|
15
16
|
module ScopeEnforcement
|
|
@@ -54,6 +55,27 @@ module AgentAdmit
|
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
|
|
58
|
+
##
|
|
59
|
+
# Enforce human-presence verification -- agent's connection MUST have been
|
|
60
|
+
# authorized with a completed WebAuthn presence ceremony or gets 403.
|
|
61
|
+
# Fail closed: connections from servers that predate the presence feature
|
|
62
|
+
# (no presence block) are treated as not verified.
|
|
63
|
+
#
|
|
64
|
+
def require_presence!
|
|
65
|
+
unless request.env["agentadmit.auth_type"] == "agent"
|
|
66
|
+
render json: { error: "invalid_token", error_description: "AgentAdmit token required" }, status: :unauthorized
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
presence = request.env["agentadmit.presence"]
|
|
71
|
+
unless presence.is_a?(Hash) && presence["verified"] == true
|
|
72
|
+
render json: {
|
|
73
|
+
error: "presence_required",
|
|
74
|
+
error_description: "This action requires a connection authorized with human presence verification."
|
|
75
|
+
}, status: :forbidden
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
57
79
|
##
|
|
58
80
|
# Get the current agent/user context.
|
|
59
81
|
#
|
|
@@ -64,6 +86,7 @@ module AgentAdmit
|
|
|
64
86
|
scopes: request.env["agentadmit.scopes"],
|
|
65
87
|
connection_id: request.env["agentadmit.connection_id"],
|
|
66
88
|
agent_label: request.env["agentadmit.agent_label"],
|
|
89
|
+
presence: request.env["agentadmit.presence"],
|
|
67
90
|
}
|
|
68
91
|
end
|
|
69
92
|
end
|
data/lib/agentadmit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agentadmit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Christopher Emerson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|