senec 0.22.0 → 0.23.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.
@@ -40,8 +40,8 @@ module Senec
40
40
  )
41
41
 
42
42
  # Manual HTTP needed for Keycloak cross-domain form handling
43
- login_form_url = fetch_login_form_url(auth_url)
44
- redirect_url = submit_credentials(login_form_url)
43
+ form_type, form_url = fetch_login_form(auth_url)
44
+ redirect_url = submit_credentials(form_type, form_url)
45
45
  authorization_code = extract_authorization_code(redirect_url)
46
46
 
47
47
  self.oauth_token =
@@ -80,15 +80,19 @@ module Senec
80
80
 
81
81
  attr_accessor :oauth_token
82
82
 
83
- def fetch_login_form_url(auth_url)
83
+ def fetch_login_form(auth_url)
84
84
  response = http_request(:get, auth_url)
85
85
  store_cookies(response) # Required for Keycloak CSRF protection
86
- extract_login_form_action_url(response.body) || raise('Login form not found')
86
+ detect_login_form(response.body) || raise('Login form not found')
87
87
  end
88
88
 
89
- def submit_credentials(form_url)
90
- credentials = { username:, password: }
91
- response = http_request(:post, form_url, data: credentials)
89
+ def submit_credentials(form_type, form_url)
90
+ case form_type
91
+ when :username_and_password
92
+ response = http_request(:post, form_url, data: { username:, password: })
93
+ when :username_only
94
+ response = submit_username_then_password(form_url)
95
+ end
92
96
 
93
97
  if response.status == 200
94
98
  # Check if MFA is required
@@ -108,6 +112,20 @@ module Senec
108
112
  handle_redirect_response(response)
109
113
  end
110
114
 
115
+ def submit_username_then_password(form_url)
116
+ response = http_request(:post, form_url, data: { username: })
117
+ store_cookies(response)
118
+
119
+ raise 'Password form not found' unless response.status == 200
120
+
121
+ password_form_url = find_form_action_url(response.body) do |f|
122
+ f.match(/name=["']?password["']?/i)
123
+ end
124
+ raise 'Password form not found' unless password_form_url
125
+
126
+ http_request(:post, password_form_url, data: { username:, password: })
127
+ end
128
+
111
129
  def submit_totp_form(form_url)
112
130
  totp = build_totp_from_uri(totp_uri)
113
131
 
@@ -124,12 +142,21 @@ module Senec
124
142
  params['code'] || raise('No authorization code found')
125
143
  end
126
144
 
127
- def extract_login_form_action_url(html)
128
- find_form_action_url(html) do |form|
129
- # Look for a form with username and password fields
145
+ def detect_login_form(html)
146
+ # Check for combined form first (backward compatible with old SENEC flow)
147
+ url = find_form_action_url(html) do |form|
130
148
  form.match(/name=["']?username["']?/i) &&
131
149
  form.match(/name=["']?password["']?/i)
132
150
  end
151
+ return [:username_and_password, url] if url
152
+
153
+ # Check for username-only form (new two-step SENEC flow)
154
+ url = find_form_action_url(html) do |form|
155
+ form.match(/name=["']?username["']?/i)
156
+ end
157
+ return [:username_only, url] if url
158
+
159
+ nil
133
160
  end
134
161
 
135
162
  def extract_totp_form_action_url(html)
@@ -169,48 +196,43 @@ module Senec
169
196
  response.headers['location'] || raise('No redirect location')
170
197
  end
171
198
 
172
- def ensure_token_valid
199
+ def ensure_token_valid!
173
200
  authenticate! unless authenticated?
174
- return true unless oauth_token.expired?
201
+ return unless oauth_token.expired?
175
202
 
176
203
  self.oauth_token = oauth_token.refresh!
177
- true
178
204
  rescue StandardError => e
179
- # :nocov:
180
- warn "Token refresh failed: #{e.message}"
181
- false
182
- # :nocov:
205
+ warn "Token refresh failed: #{e.message}, trying to re-authenticate..."
206
+
207
+ authenticate!
183
208
  end
184
209
 
185
- def get(url, default: nil)
186
- return default unless ensure_token_valid
210
+ def get(url)
211
+ ensure_token_valid!
187
212
 
188
213
  response = oauth_token.get(url)
189
- return default unless response.status == 200
190
-
191
214
  JSON.parse(response.body)
192
215
  rescue StandardError => e
193
216
  # :nocov:
194
217
  warn "API error: #{e.message}"
195
- default
218
+ nil
196
219
  # :nocov:
197
220
  end
198
221
 
199
- def post(url, data, default: nil)
200
- return default unless ensure_token_valid
222
+ def post(url, data)
223
+ ensure_token_valid!
201
224
 
202
225
  response = oauth_token.post(
203
226
  url,
204
227
  body: data.to_json,
205
228
  headers: { 'Content-Type' => 'application/json' },
206
229
  )
207
- return default unless response.status == 200
208
230
 
209
231
  JSON.parse(response.body)
210
232
  rescue StandardError => e
211
233
  # :nocov:
212
234
  warn "API error: #{e.message}"
213
- default
235
+ nil
214
236
  # :nocov:
215
237
  end
216
238
 
data/lib/senec/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Senec
2
- VERSION = '0.22.0'.freeze
2
+ VERSION = '0.23.0'.freeze
3
3
  end
Binary file
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: senec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georg Ledermann
@@ -96,8 +96,8 @@ files:
96
96
  - ".ruby-lsp/.gitignore"
97
97
  - ".ruby-lsp/Gemfile"
98
98
  - ".ruby-lsp/Gemfile.lock"
99
+ - ".ruby-lsp/last_updated"
99
100
  - ".ruby-lsp/main_lockfile_hash"
100
- - ".ruby-lsp/needs_update"
101
101
  - ".vscode/settings.json"
102
102
  - CODE_OF_CONDUCT.md
103
103
  - LICENSE
@@ -164,6 +164,8 @@ files:
164
164
  - pkg/senec-0.2.0.gem
165
165
  - pkg/senec-0.20.0.gem
166
166
  - pkg/senec-0.21.0.gem
167
+ - pkg/senec-0.22.0.gem
168
+ - pkg/senec-0.22.1.gem
167
169
  - pkg/senec-0.3.0.gem
168
170
  - pkg/senec-0.4.0.gem
169
171
  - pkg/senec-0.5.0.gem
@@ -197,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
199
  - !ruby/object:Gem::Version
198
200
  version: '0'
199
201
  requirements: []
200
- rubygems_version: 3.7.1
202
+ rubygems_version: 4.0.7
201
203
  specification_version: 4
202
204
  summary: Unofficial Ruby Client for SENEC Home
203
205
  test_files: []
File without changes