omniauth-honin 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ba9cb60419c162b8451a3fcb5b2071ac8d5229638d98422b93dbfa9f02e4666
4
- data.tar.gz: 43893c67e00b951e240e352c2d08f287d2dcadcb85820d00301c9e6cb7c0a479
3
+ metadata.gz: ac29cb208ee56c02f6d29e9364c57e3093a7fdc9b776c42a88a7c5d31a7ab395
4
+ data.tar.gz: 67fc811523c4ddf6dd690b0019b14ea9333f47c79139f6e0049ff93c0763a77d
5
5
  SHA512:
6
- metadata.gz: e3aead620f5c9a95604c2714811558d783fadc3940ac29c63f6e8c441c67e414977c7174a9db5686edb9ed7743ee67dc6d5809b4e57874c99bf83d8233ba102a
7
- data.tar.gz: 0a5bdaa70720965c09c80ba533ce5ea47fa18d11c55fd9fcc2f4279f59b29c1add213dd19aeaf8404f62fe093d82fbdcf3d1e8d41398b8b824e1659eb842e5fa
6
+ metadata.gz: 1cdb6461a0e684528666698e1f67a1ef6ef07c02d708fc2f30ad998998dd1620d5505a4148c8e0a6deccd274fb443eb0d08b56fc707774f42d027e850378ad34
7
+ data.tar.gz: c5562a1b8c375dabf8578407e35d0ab10fb7a4bf4636b488b827be0b7dc1c3163ba7eee7a48a8713cf821d7c7e07f6a65df438539e67cce1e18f333be406f824
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAuth
4
4
  module Honin
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -2,13 +2,7 @@
2
2
 
3
3
  require "omniauth"
4
4
  require "omniauth-oauth2"
5
- require "jwt"
6
- require "securerandom"
7
- require "base64"
8
- require "digest"
9
- require "net/http"
10
- require "uri"
11
- require "json"
5
+ require "honin/client"
12
6
 
13
7
  module OmniAuth
14
8
  module Strategies
@@ -20,8 +14,7 @@ module OmniAuth
20
14
  authorize_url: "/oauth/authorize",
21
15
  token_url: "/oauth/token"
22
16
 
23
- # Disabled so we own the full PKCE flow; omniauth-oauth2's built-in
24
- # implementation would conflict with our session keys and S256 enforcement.
17
+ # Disabled we own the full PKCE flow via HoninClient::PKCE.
25
18
  option :pkce, false
26
19
 
27
20
  option :scope, "email profile"
@@ -29,19 +22,22 @@ module OmniAuth
29
22
  # Honin only supports client_secret_post, not client_secret_basic.
30
23
  option :auth_token_params, {client_secret_param: "client_secret"}
31
24
 
32
- # Sub-path mount support. Set to the engine's mount path when the Honin
33
- # instance is not at the root — e.g. "/auth" for id.acme.co/auth/...
34
- # The issuer claim in the JWT is verified against site + base_path.
35
- # Leave empty (default) for honin.id and root-mounted instances.
25
+ # Sub-path mount support. Set when the Honin instance is not at the root
26
+ # (e.g. "/auth" for id.acme.co/auth/...). The issuer claim in the JWT is
27
+ # verified against site + base_path. Leave empty for honin.id.
36
28
  option :base_path, ""
37
29
 
30
+ # Provide a JWKS URI to enable RS256 JWT verification (recommended).
38
31
  option :jwks_uri, nil
39
- option :jwks, nil
40
32
 
41
- PKCE_VERIFIER_LENGTH = 64
33
+ # Alternatively, supply a pre-loaded JWK set (Hash, Array, or JWT::JWK::Set).
34
+ # Useful for self-hosted instances with a known public key.
35
+ option :jwks, nil
42
36
 
43
37
  def request_phase
44
- generate_pkce!
38
+ pkce = HoninClient::PKCE.new
39
+ session[:honin_pkce_verifier] = pkce.code_verifier
40
+ session[:honin_pkce_challenge] = pkce.code_challenge
45
41
  super
46
42
  end
47
43
 
@@ -61,30 +57,31 @@ module OmniAuth
61
57
  def callback_phase
62
58
  super
63
59
  ensure
64
- clear_pkce_session!
60
+ session.delete(:honin_pkce_verifier)
61
+ session.delete(:honin_pkce_challenge)
65
62
  end
66
63
 
67
- uid { decoded_jwt["sub"] }
64
+ uid { honin_identity.sub }
68
65
 
69
66
  info do
70
- jwt = decoded_jwt
67
+ id = honin_identity
71
68
  {
72
- account_type: jwt["account_type"],
73
- name: jwt["display_name"],
74
- email: jwt["email"],
75
- email_verified: jwt["email_verified"],
76
- timezone: jwt["timezone"],
77
- locale: jwt["locale"]
69
+ account_type: id.account_type,
70
+ name: id.display_name,
71
+ email: id.email,
72
+ email_verified: id.email_verified,
73
+ timezone: id.timezone,
74
+ locale: id.locale
78
75
  }.compact
79
76
  end
80
77
 
81
78
  extra do
82
- jwt = decoded_jwt
79
+ id = honin_identity
83
80
  {
84
- raw_info: raw_info,
85
- granted_scopes: jwt["granted_scopes"],
86
- required_scopes: jwt["required_scopes"]
87
- }.compact
81
+ raw_info: id.to_h,
82
+ granted_scopes: id.granted_scopes,
83
+ required_scopes: id.required_scopes
84
+ }
88
85
  end
89
86
 
90
87
  def client
@@ -94,98 +91,34 @@ module OmniAuth
94
91
  token_url: "#{options.base_path}/oauth/token")
95
92
  end
96
93
 
97
- def raw_info
98
- @raw_info ||= decoded_jwt
99
- end
100
-
101
- def decoded_jwt
102
- @decoded_jwt ||= begin
103
- token = access_token.token
104
-
105
- if jwks_verification_available?
106
- decode_verified_jwt(token)
107
- else
108
- decode_unverified_jwt(token)
109
- end
94
+ def honin_identity
95
+ @honin_identity ||= if options.jwks_uri && !options.jwks_uri.to_s.empty?
96
+ build_verifier(self.class.jwks_cache_for(options.jwks_uri)).verify(access_token.token)
97
+ elsif options.jwks
98
+ build_verifier(HoninClient::JwksCache::Static.new(options.jwks)).verify(access_token.token)
99
+ else
100
+ # No JWKS configured — decode without verification (dev/test only)
101
+ payload, = JWT.decode(access_token.token, nil, false)
102
+ HoninClient::Identity.new(payload)
110
103
  end
111
- end
112
-
113
- private
114
-
115
- def generate_pkce!
116
- verifier = SecureRandom.urlsafe_base64(PKCE_VERIFIER_LENGTH)
117
- challenge = Base64.urlsafe_encode64(
118
- Digest::SHA256.digest(verifier),
119
- padding: false
120
- )
121
-
122
- session[:honin_pkce_verifier] = verifier
123
- session[:honin_pkce_challenge] = challenge
124
- end
125
-
126
- def clear_pkce_session!
127
- session.delete(:honin_pkce_verifier)
128
- session.delete(:honin_pkce_challenge)
129
- end
130
-
131
- def jwks_verification_available?
132
- (options.jwks_uri && !options.jwks_uri.to_s.empty?) ||
133
- (!options.jwks.nil? && options.jwks != false)
134
- end
135
-
136
- def decode_verified_jwt(token)
137
- decode_opts = {
138
- algorithms: ["RS256"],
139
- verify_iss: true,
140
- iss: "#{options.client_options.site}#{options.base_path}",
141
- verify_aud: true,
142
- aud: options.client_id
143
- }
144
-
145
- payload, = JWT.decode(token, nil, true, decode_opts.merge(jwks: resolve_jwk_set))
146
- payload
147
- rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature,
148
- JWT::InvalidIssuerError, JWT::JWKError => e
104
+ rescue HoninClient::Error => e
149
105
  raise OmniAuth::Strategies::OAuth2::CallbackError.new(:jwt_verification_failed, e.message)
150
106
  end
151
107
 
152
- def decode_unverified_jwt(token)
153
- payload, = JWT.decode(token, nil, false)
154
- payload
155
- end
156
-
157
- def resolve_jwk_set
158
- if options.jwks_uri && !options.jwks_uri.to_s.empty?
159
- fetch_jwks(options.jwks_uri)
160
- else
161
- coerce_jwks(options.jwks)
162
- end
108
+ # Class-level JWKS cache — persists across requests, keyed by URI.
109
+ def self.jwks_cache_for(uri)
110
+ @jwks_caches ||= {}
111
+ @jwks_caches[uri] ||= HoninClient::JwksCache.new(uri)
163
112
  end
164
113
 
165
- def coerce_jwks(jwks)
166
- case jwks
167
- when JWT::JWK::Set
168
- jwks
169
- when Hash
170
- JWT::JWK::Set.new(jwks[:keys] || jwks["keys"] || [jwks])
171
- when Array
172
- JWT::JWK::Set.new(jwks)
173
- else
174
- raise ArgumentError, "jwks must be a Hash, Array, or JWT::JWK::Set, got #{jwks.class}"
175
- end
176
- end
114
+ private
177
115
 
178
- def fetch_jwks(uri)
179
- response = Net::HTTP.get_response(URI.parse(uri))
180
- unless response.is_a?(Net::HTTPSuccess)
181
- raise OmniAuth::Strategies::OAuth2::CallbackError.new(
182
- :jwks_fetch_failed, "HTTP #{response.code}"
183
- )
184
- end
185
- body = JSON.parse(response.body)
186
- JWT::JWK::Set.new(body["keys"] || [body])
187
- rescue JSON::ParserError, JWT::JWKError => e
188
- raise OmniAuth::Strategies::OAuth2::CallbackError.new(:jwks_fetch_failed, e.message)
116
+ def build_verifier(cache)
117
+ HoninClient::TokenVerifier.new(
118
+ jwks_cache: cache,
119
+ issuer: "#{options.client_options.site}#{options.base_path}",
120
+ client_id: options.client_id
121
+ )
189
122
  end
190
123
  end
191
124
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-honin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Axel Gustav
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-04-21 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: omniauth
@@ -38,19 +38,19 @@ dependencies:
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.8'
40
40
  - !ruby/object:Gem::Dependency
41
- name: jwt
41
+ name: honin-client
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '2.7'
46
+ version: 0.1.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '2.7'
53
+ version: 0.1.0
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: rake
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -169,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
169
  - !ruby/object:Gem::Version
170
170
  version: '0'
171
171
  requirements: []
172
- rubygems_version: 3.6.3
172
+ rubygems_version: 3.6.9
173
173
  specification_version: 4
174
174
  summary: Honin OAuth2 strategy for OmniAuth
175
175
  test_files: []