Class: Iron::Oauth::Grant
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Iron::Oauth::Grant
- Defined in:
- app/models/iron/oauth/grant.rb
Class Method Summary collapse
- .issue!(client:, user:, redirect_uri:, scopes:, code_challenge:, resource:, code_challenge_method: "S256", ttl: 10.minutes) ⇒ Object
-
.redeem(code, client:, redirect_uri:, verifier:, resource:) ⇒ Object
Consumes the code only once the exchange is fully bound to the client, redirect URI, and PKCE verifier — all inside the lock.
Instance Method Summary collapse
- #active? ⇒ Boolean
- #expired? ⇒ Boolean
- #pkce_verified?(verifier) ⇒ Boolean
- #redeemable_by?(client:, redirect_uri:, verifier:, resource:) ⇒ Boolean
Class Method Details
.issue!(client:, user:, redirect_uri:, scopes:, code_challenge:, resource:, code_challenge_method: "S256", ttl: 10.minutes) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'app/models/iron/oauth/grant.rb', line 9 def self.issue!(client:, user:, redirect_uri:, scopes:, code_challenge:, resource:, code_challenge_method: "S256", ttl: 10.minutes) code = Secret.generate grant = create!( client: client, user: user, redirect_uri: redirect_uri, scopes: scopes, code_challenge: code_challenge, code_challenge_method: code_challenge_method, resource: resource, code_digest: Secret.digest(code), expires_at: ttl.from_now ) [ grant, code ] end |
.redeem(code, client:, redirect_uri:, verifier:, resource:) ⇒ Object
Consumes the code only once the exchange is fully bound to the client, redirect URI, and PKCE verifier — all inside the lock. Validating before marking it redeemed stops an interceptor without the verifier from burning a legitimate client's code. When a block is given, the token is issued in the same transaction, so a failure there rolls the redemption back and leaves the code usable for a retry.
31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'app/models/iron/oauth/grant.rb', line 31 def self.redeem(code, client:, redirect_uri:, verifier:, resource:) grant = find_by(code_digest: Secret.digest(code)) return unless grant result = nil grant.with_lock do if grant.redeemable_by?(client:, redirect_uri:, verifier:, resource:) grant.update!(redeemed_at: Time.current) result = block_given? ? yield(grant) : grant end end result end |
Instance Method Details
#active? ⇒ Boolean
62 63 64 |
# File 'app/models/iron/oauth/grant.rb', line 62 def active? redeemed_at.nil? && !expired? end |
#expired? ⇒ Boolean
58 59 60 |
# File 'app/models/iron/oauth/grant.rb', line 58 def expired? expires_at.past? end |
#pkce_verified?(verifier) ⇒ Boolean
53 54 55 56 |
# File 'app/models/iron/oauth/grant.rb', line 53 def pkce_verified?(verifier) expected = Base64.urlsafe_encode64(OpenSSL::Digest::SHA256.digest(verifier.to_s), padding: false) ActiveSupport::SecurityUtils.secure_compare(expected, code_challenge) end |
#redeemable_by?(client:, redirect_uri:, verifier:, resource:) ⇒ Boolean
45 46 47 48 49 50 51 |
# File 'app/models/iron/oauth/grant.rb', line 45 def redeemable_by?(client:, redirect_uri:, verifier:, resource:) active? && client_id == client.id && self.redirect_uri == redirect_uri && self.resource == resource && pkce_verified?(verifier) end |