ruby_native 0.14.3 → 0.15.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: 20484032681be68def5c45552a5ac02502f3f3eaf63e69b7cc5fffbd785177cc
4
- data.tar.gz: d6e34e9592f9ec3f4a14ad739c1f468c5f81d75784411742e2cd317a3c57a093
3
+ metadata.gz: e8d87be28530140900677b188eb358c7a1dedaa5966760494cd714d58f988017
4
+ data.tar.gz: 6b2842f30c59244680a0b6a853d9c43617c33dda0cb1232089159fdc06fbf757
5
5
  SHA512:
6
- metadata.gz: e34b666cebd831fc7ed1946090dc68562807a1bbc524f86781af3b56f46f318394104e745270ab7914778ccf471caeb495b92912fa62152760b7bcf1d1d0e912
7
- data.tar.gz: 5de7b5d12c50b1bb6c48bbdf333847da2b4ae0b20730beb4c3f3bd7059b1dc3d1225ec1e34f9afe0dbf167b3f76aa04d46aed1e9395f6985e16ddcf4e8a0cbcb
6
+ metadata.gz: 79c71e7a97da93357aa8e927a41dbdd5742919dcfd9bdfddc7b2d0e215c37489bacfd6b3cdf77b2b1b86e13f0a081777830c732d0ebbacc52398a8026609ff05
7
+ data.tar.gz: 1d5946a4083783f961363b05e61f3e3c62909d60c90108cc4e67594149a559fa62810507dd4720ea67fb9cb7e6c2a8c0e7198ed0593b0d7fdea140afc1145476
@@ -230,6 +230,13 @@ module RubyNative
230
230
  tag.div(data: { native_overscroll_top: top, native_overscroll_bottom: bottom || top }, hidden: true)
231
231
  end
232
232
 
233
+ # Hides the iOS keyboard toolbar (the previous/next/Done bar) on this page.
234
+ # Page-level: the toolbar belongs to the web view, not to individual fields.
235
+ def native_keyboard_tag(toolbar: false)
236
+ return "".html_safe if toolbar
237
+ tag.div(data: { native_keyboard_toolbar: false }, hidden: true)
238
+ end
239
+
233
240
  def native_haptic_data(feedback = :success, **data)
234
241
  feedback = feedback.to_s
235
242
  feedback = "success" if feedback.empty?
@@ -18,7 +18,7 @@ module RubyNative
18
18
  request = ActionDispatch::Request.new(env)
19
19
  on_oauth_path = oauth_path?(request)
20
20
  started_native_oauth = on_oauth_path && request.params["ruby_native"] == "1"
21
- callback_scheme = permitted_scheme(request.params["callback_scheme"]) if started_native_oauth
21
+ param_scheme = permitted_scheme(request.params["callback_scheme"]) if started_native_oauth
22
22
 
23
23
  status, headers, body = @app.call(env)
24
24
 
@@ -26,27 +26,27 @@ module RubyNative
26
26
  relax_cookie_samesite!(headers)
27
27
  end
28
28
 
29
- if started_native_oauth && callback_scheme.present? && redirect?(status)
29
+ if started_native_oauth && param_scheme.present? && redirect?(status)
30
30
  Rails.logger.debug { "[RubyNative] OAuth started for #{request.path}, setting tracking cookie" }
31
- set_cookie(headers, callback_scheme)
31
+ set_cookie(headers, param_scheme)
32
32
  end
33
33
 
34
- stored_scheme = permitted_scheme(read_cookie(request))
34
+ # On a start request the param names the app asking right now, so a stale
35
+ # cookie from another app on the same domain must never win.
36
+ callback_scheme = started_native_oauth ? param_scheme : permitted_scheme(read_cookie(request))
35
37
 
36
- if stored_scheme && redirect?(status)
38
+ if callback_scheme && redirect?(status)
37
39
  location = headers["location"] || headers["Location"]
38
40
 
39
41
  if auth_failure?(location)
40
42
  Rails.logger.info { "[RubyNative] OAuth failed, redirecting to native app" }
41
- delete_cookie(headers)
42
- return redirect_to_native(stored_scheme, error: true)
43
+ return redirect_to_native(callback_scheme, error: true)
43
44
  end
44
45
 
45
46
  if internal_redirect?(request, location)
46
47
  token = build_token(headers, location)
47
48
  Rails.logger.info { "[RubyNative] OAuth succeeded, redirecting to native app" }
48
- delete_cookie(headers)
49
- return redirect_to_native(stored_scheme, token: token)
49
+ return redirect_to_native(callback_scheme, token: token)
50
50
  end
51
51
  end
52
52
 
@@ -116,8 +116,14 @@ module RubyNative
116
116
  nil
117
117
  end
118
118
 
119
- def delete_cookie(headers)
120
- Rack::Utils.delete_set_cookie_header!(headers, COOKIE_NAME, path: "/")
119
+ # Attributes must match set_cookie or browsers keep the original cookie.
120
+ def expire_cookie(headers)
121
+ headers["set-cookie"] = Rack::Utils.delete_set_cookie_header(COOKIE_NAME, {
122
+ path: "/",
123
+ httponly: true,
124
+ secure: true,
125
+ same_site: :none
126
+ })
121
127
  end
122
128
 
123
129
  def verifier
@@ -133,16 +139,12 @@ module RubyNative
133
139
  # breaks OmniAuth's state verification. Relax existing cookies
134
140
  # to SameSite=None so the session cookie survives Apple's callback.
135
141
  def relax_cookie_samesite!(headers)
136
- raw = headers["set-cookie"]
137
- return unless raw
138
-
139
- cookies = raw.is_a?(Array) ? raw : raw.split("\n")
140
- headers["set-cookie"] = cookies.map { |cookie|
142
+ SetCookieHeader.rewrite!(headers) do |cookie|
141
143
  next cookie unless cookie.match?(/SameSite=Lax/i)
142
144
  cookie.gsub(/SameSite=Lax/i, "SameSite=None").then { |c|
143
145
  c.include?("Secure") ? c : "#{c}; Secure"
144
146
  }
145
- }.join("\n")
147
+ end
146
148
  end
147
149
 
148
150
  def redirect?(status)
@@ -165,23 +167,22 @@ module RubyNative
165
167
  end
166
168
 
167
169
  def build_token(headers, redirect_url)
168
- raw_cookies = headers["set-cookie"] || headers["Set-Cookie"]
169
- cookies = case raw_cookies
170
- when String then raw_cookies.split("\n")
171
- when Array then raw_cookies
172
- else []
173
- end
170
+ # Restoring the tracking cookie into the app's cookie jar would hijack the
171
+ # next in-app redirect, so it never rides along in the token.
172
+ cookies = SetCookieHeader.read(headers).reject { |c| c.start_with?("#{COOKIE_NAME}=") }
174
173
 
175
174
  redirect_url = "/" if auth_start_path?(redirect_url)
176
175
 
177
- Rails.logger.info { "[RubyNative] Captured #{cookies.size} cookies for token (raw type: #{raw_cookies.class})" }
176
+ Rails.logger.info { "[RubyNative] Captured #{cookies.size} cookies for token" }
178
177
 
179
178
  self.class.build_token(cookies: cookies, redirect_url: redirect_url)
180
179
  end
181
180
 
182
181
  def redirect_to_native(callback_scheme, token: nil, error: false)
183
182
  query = error ? "error=true" : "token=#{CGI.escape(token)}"
184
- [302, {"location" => "#{callback_scheme}://auth/callback?#{query}"}, [""]]
183
+ headers = {"location" => "#{callback_scheme}://auth/callback?#{query}"}
184
+ expire_cookie(headers)
185
+ [302, headers, [""]]
185
186
  end
186
187
 
187
188
  def auth_start_path?(url)
@@ -0,0 +1,26 @@
1
+ module RubyNative
2
+ # Rack 2 carries Set-Cookie as one newline joined String, Rack 3 as an Array
3
+ # of cookie strings. Rewrites must hand back whichever shape came in.
4
+ module SetCookieHeader
5
+ KEYS = ["set-cookie", "Set-Cookie"].freeze
6
+
7
+ module_function
8
+
9
+ def read(headers)
10
+ raw = KEYS.map { |key| headers[key] }.compact.first
11
+ case raw
12
+ when Array then raw
13
+ when String then raw.split("\n")
14
+ else []
15
+ end
16
+ end
17
+
18
+ def rewrite!(headers, &block)
19
+ key = KEYS.find { |k| headers[k] }
20
+ return unless key
21
+
22
+ rewritten = read(headers).map(&block)
23
+ headers[key] = headers[key].is_a?(Array) ? rewritten : rewritten.join("\n")
24
+ end
25
+ end
26
+ end
@@ -34,10 +34,9 @@ module RubyNative
34
34
  end
35
35
 
36
36
  def strip_cookie_domain!(headers)
37
- raw = headers["set-cookie"]
38
- cookies = raw.is_a?(Array) ? raw : raw.split("\n")
39
- stripped = cookies.map { |cookie| cookie.gsub(/;\s*domain=[^;]*/i, "") }
40
- headers["set-cookie"] = (stripped.length == 1) ? stripped.first : stripped
37
+ SetCookieHeader.rewrite!(headers) do |cookie|
38
+ cookie.gsub(/;\s*domain=[^;]*/i, "")
39
+ end
41
40
  end
42
41
  end
43
42
  end
@@ -1,3 +1,3 @@
1
1
  module RubyNative
2
- VERSION = "0.14.3"
2
+ VERSION = "0.15.0"
3
3
  end
data/lib/ruby_native.rb CHANGED
@@ -4,6 +4,7 @@ require "ruby_native/helper"
4
4
  require "ruby_native/native_version"
5
5
  require "ruby_native/native_detection"
6
6
  require "ruby_native/inertia_support"
7
+ require "ruby_native/set_cookie_header"
7
8
  require "ruby_native/oauth_middleware"
8
9
  require "ruby_native/tunnel_cookie_middleware"
9
10
  require "ruby_native/iap/event"
@@ -181,17 +182,22 @@ module RubyNative
181
182
  # "callback" and send sign-in into a loop. The callback round-trip is handled
182
183
  # automatically by OAuthMiddleware's tracking cookie, so it never needs
183
184
  # listing. Drop any entry that is the "/callback" child of another listed
184
- # path and warn, so a copied-in callback can't break native sign-in.
185
+ # path and warn, so a copied-in callback can't break native sign-in. The value
186
+ # is always written back as an array: a scalar in YAML fails the whole native
187
+ # config decode on both platforms.
185
188
  def self.normalize_oauth_paths
186
- paths = Array(self.config.dig(:auth, :oauth_paths))
187
- callbacks = paths.select { |path| paths.any? { |start| path == "#{start}/callback" } }
188
- return if callbacks.empty?
189
+ auth = self.config[:auth]
190
+ return unless auth.is_a?(Hash) && auth.key?(:oauth_paths)
189
191
 
190
- Rails.logger.warn(
191
- "[RubyNative] Ignoring OAuth callback path(s) in config/ruby_native.yml " \
192
- "(#{callbacks.join(", ")}). List only the authorize path; callbacks are handled automatically."
193
- )
194
- self.config[:auth][:oauth_paths] = paths - callbacks
192
+ paths = Array(auth[:oauth_paths])
193
+ callbacks = paths.select { |path| paths.any? { |start| path == "#{start}/callback" } }
194
+ if callbacks.any?
195
+ Rails.logger.warn(
196
+ "[RubyNative] Ignoring OAuth callback path(s) in config/ruby_native.yml " \
197
+ "(#{callbacks.join(", ")}). List only the authorize path; callbacks are handled automatically."
198
+ )
199
+ end
200
+ auth[:oauth_paths] = paths - callbacks
195
201
  end
196
202
 
197
203
  # Entries are path prefixes: "/pair/" links every URL under it. A leading
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.3
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Masilotti
@@ -109,6 +109,7 @@ files:
109
109
  - lib/ruby_native/native_version.rb
110
110
  - lib/ruby_native/oauth_middleware.rb
111
111
  - lib/ruby_native/screenshots/sign_in_helper.rb
112
+ - lib/ruby_native/set_cookie_header.rb
112
113
  - lib/ruby_native/tunnel_cookie_middleware.rb
113
114
  - lib/ruby_native/version.rb
114
115
  homepage: https://rubynative.com