ruby_native 0.1.2 → 0.1.3
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/app/controllers/ruby_native/auth/sessions_controller.rb +26 -0
- data/app/controllers/ruby_native/auth/start_controller.rb +16 -0
- data/app/views/ruby_native/auth/start/show.html.erb +16 -0
- data/config/routes.rb +4 -0
- data/lib/ruby_native/engine.rb +4 -0
- data/lib/ruby_native/oauth_middleware.rb +144 -0
- data/lib/ruby_native/version.rb +1 -1
- data/lib/ruby_native.rb +2 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20d760214b3f22f2ad779114c93fea5123415f54eae344bd4b8615704a5abc06
|
|
4
|
+
data.tar.gz: a840dcc62eb743a8319646fbc31429b62ec3bcd42c5927cbecf51bb1e72c114c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8566892b7a575d4b60fcda366827f663ac9b8ca66b2b80e4133f432434687f2b73b1276140e1b7fe3134f77bbab3daf60e136d8b3c6b4c28acb6cfc8079ca1a3
|
|
7
|
+
data.tar.gz: 48733345d9bf04179752359e7c3fd20ed9f72b1cbd5ed94983b0f5b1f6a74e5fc585bc4edda37b51425759ef793e4e39aeb2cd3112acc34b6fa3a3ccccf315f6
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module RubyNative
|
|
2
|
+
module Auth
|
|
3
|
+
class SessionsController < ::ActionController::Base
|
|
4
|
+
def show
|
|
5
|
+
data = OAuthMiddleware.read_token(params[:token])
|
|
6
|
+
|
|
7
|
+
unless data
|
|
8
|
+
Rails.logger.debug { "[RubyNative] OAuth token exchange failed: invalid or expired token" }
|
|
9
|
+
head :unauthorized
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Prevent the session middleware from appending its own (empty)
|
|
14
|
+
# session cookie, which would overwrite the authenticated one.
|
|
15
|
+
request.session_options[:skip] = true
|
|
16
|
+
|
|
17
|
+
if data[:cookies].present?
|
|
18
|
+
response.headers["set-cookie"] = data[:cookies].join("\n")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Rails.logger.debug { "[RubyNative] OAuth token exchanged, redirecting to #{data[:redirect_url]}" }
|
|
22
|
+
render json: {redirect_url: data[:redirect_url]}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module RubyNative
|
|
2
|
+
module Auth
|
|
3
|
+
class StartController < ::ActionController::Base
|
|
4
|
+
def show
|
|
5
|
+
@provider = params[:provider]
|
|
6
|
+
|
|
7
|
+
unless @provider.match?(/\A[a-z0-9_]+\z/)
|
|
8
|
+
head :bad_request
|
|
9
|
+
return
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
@callback_scheme = params[:callback_scheme]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Signing in…</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<p>Signing in…</p>
|
|
10
|
+
<%= form_tag "/auth/#{@provider}", method: :post, id: "oauth-form" do %>
|
|
11
|
+
<input type="hidden" name="ruby_native" value="1">
|
|
12
|
+
<input type="hidden" name="callback_scheme" value="<%= @callback_scheme %>">
|
|
13
|
+
<% end %>
|
|
14
|
+
<script>document.getElementById("oauth-form").submit();</script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
data/config/routes.rb
CHANGED
data/lib/ruby_native/engine.rb
CHANGED
|
@@ -28,6 +28,10 @@ module RubyNative
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
initializer "ruby_native.oauth_middleware" do |app|
|
|
32
|
+
app.middleware.insert_before ActionDispatch::Cookies, RubyNative::OAuthMiddleware
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
initializer "ruby_native.routes" do |app|
|
|
32
36
|
app.routes.prepend do
|
|
33
37
|
mount RubyNative::Engine, at: "/native"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module RubyNative
|
|
2
|
+
class OAuthMiddleware
|
|
3
|
+
COOKIE_NAME = "_ruby_native_oauth"
|
|
4
|
+
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
request = ActionDispatch::Request.new(env)
|
|
11
|
+
started_oauth = oauth_start_request?(request)
|
|
12
|
+
callback_scheme = request.params["callback_scheme"] if started_oauth
|
|
13
|
+
|
|
14
|
+
status, headers, body = @app.call(env)
|
|
15
|
+
|
|
16
|
+
if started_oauth && callback_scheme.present? && redirect?(status)
|
|
17
|
+
Rails.logger.debug { "[RubyNative] OAuth started for #{request.path}, setting tracking cookie" }
|
|
18
|
+
set_cookie(headers, callback_scheme)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
stored_scheme = read_cookie(request)
|
|
22
|
+
|
|
23
|
+
if stored_scheme && redirect?(status)
|
|
24
|
+
location = headers["location"] || headers["Location"]
|
|
25
|
+
|
|
26
|
+
if auth_failure?(location)
|
|
27
|
+
Rails.logger.info { "[RubyNative] OAuth failed, redirecting to native app" }
|
|
28
|
+
delete_cookie(headers)
|
|
29
|
+
return redirect_to_native(stored_scheme, error: true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if internal_redirect?(request, location)
|
|
33
|
+
token = build_token(headers, location)
|
|
34
|
+
Rails.logger.info { "[RubyNative] OAuth succeeded, redirecting to native app" }
|
|
35
|
+
delete_cookie(headers)
|
|
36
|
+
return redirect_to_native(stored_scheme, token: token)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
[status, headers, body]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.encryptor
|
|
44
|
+
@encryptor ||= begin
|
|
45
|
+
key = ActiveSupport::KeyGenerator.new(Rails.application.secret_key_base)
|
|
46
|
+
.generate_key("ruby_native_oauth", ActiveSupport::MessageEncryptor.key_len)
|
|
47
|
+
ActiveSupport::MessageEncryptor.new(key)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.build_token(cookies:, redirect_url:)
|
|
52
|
+
encryptor.encrypt_and_sign(
|
|
53
|
+
{cookies: cookies, redirect_url: redirect_url},
|
|
54
|
+
expires_in: 5.minutes,
|
|
55
|
+
purpose: "ruby_native_oauth"
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.read_token(token)
|
|
60
|
+
data = encryptor.decrypt_and_verify(token, purpose: "ruby_native_oauth")
|
|
61
|
+
data&.symbolize_keys
|
|
62
|
+
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def oauth_start_request?(request)
|
|
69
|
+
return false unless oauth_paths.any? { |p| request.path == p }
|
|
70
|
+
request.params["ruby_native"] == "1"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def set_cookie(headers, callback_scheme)
|
|
74
|
+
signed = verifier.generate(callback_scheme)
|
|
75
|
+
Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, {
|
|
76
|
+
value: signed,
|
|
77
|
+
path: "/",
|
|
78
|
+
httponly: true,
|
|
79
|
+
secure: Rails.env.production?,
|
|
80
|
+
same_site: :lax,
|
|
81
|
+
max_age: 300
|
|
82
|
+
})
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def read_cookie(request)
|
|
86
|
+
signed_value = request.cookies[COOKIE_NAME]
|
|
87
|
+
return nil unless signed_value.present?
|
|
88
|
+
verifier.verified(signed_value)
|
|
89
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def delete_cookie(headers)
|
|
94
|
+
Rack::Utils.delete_set_cookie_header!(headers, COOKIE_NAME, path: "/")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def verifier
|
|
98
|
+
@verifier ||= ActiveSupport::MessageVerifier.new(
|
|
99
|
+
Rails.application.secret_key_base,
|
|
100
|
+
digest: "SHA256",
|
|
101
|
+
purpose: "ruby_native_oauth"
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def redirect?(status)
|
|
106
|
+
(300..399).cover?(status)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def auth_failure?(location)
|
|
110
|
+
return false unless location
|
|
111
|
+
URI.parse(location).path == "/auth/failure"
|
|
112
|
+
rescue URI::InvalidURIError
|
|
113
|
+
false
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def internal_redirect?(request, location)
|
|
117
|
+
return false unless location
|
|
118
|
+
uri = URI.parse(location)
|
|
119
|
+
uri.host.nil? || uri.host == request.host
|
|
120
|
+
rescue URI::InvalidURIError
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def build_token(headers, redirect_url)
|
|
125
|
+
raw_cookies = headers["set-cookie"] || headers["Set-Cookie"]
|
|
126
|
+
cookies = case raw_cookies
|
|
127
|
+
when String then raw_cookies.split("\n")
|
|
128
|
+
when Array then raw_cookies
|
|
129
|
+
else []
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
self.class.build_token(cookies: cookies, redirect_url: redirect_url)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def redirect_to_native(callback_scheme, token: nil, error: false)
|
|
136
|
+
query = error ? "error=true" : "token=#{CGI.escape(token)}"
|
|
137
|
+
[302, {"location" => "#{callback_scheme}://auth/callback?#{query}"}, [""]]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def oauth_paths
|
|
141
|
+
RubyNative.config&.dig(:auth, :oauth_paths) || []
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/ruby_native/version.rb
CHANGED
data/lib/ruby_native.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "ruby_native/version"
|
|
2
2
|
require "ruby_native/helper"
|
|
3
3
|
require "ruby_native/native_detection"
|
|
4
|
+
require "ruby_native/oauth_middleware"
|
|
4
5
|
require "ruby_native/engine"
|
|
5
6
|
|
|
6
7
|
module RubyNative
|
|
@@ -13,5 +14,6 @@ module RubyNative
|
|
|
13
14
|
self.config = YAML.load_file(path).deep_symbolize_keys
|
|
14
15
|
self.config[:app] ||= {}
|
|
15
16
|
self.config[:app][:name] ||= "Ruby Native"
|
|
17
|
+
self.config[:auth] ||= {}
|
|
16
18
|
end
|
|
17
19
|
end
|
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.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joe Masilotti
|
|
@@ -49,6 +49,8 @@ files:
|
|
|
49
49
|
- LICENSE
|
|
50
50
|
- README.md
|
|
51
51
|
- app/assets/stylesheets/ruby_native.css
|
|
52
|
+
- app/controllers/ruby_native/auth/sessions_controller.rb
|
|
53
|
+
- app/controllers/ruby_native/auth/start_controller.rb
|
|
52
54
|
- app/controllers/ruby_native/config_controller.rb
|
|
53
55
|
- app/controllers/ruby_native/push/devices_controller.rb
|
|
54
56
|
- app/javascript/ruby_native/back.js
|
|
@@ -59,6 +61,7 @@ files:
|
|
|
59
61
|
- app/javascript/ruby_native/bridge/push_controller.js
|
|
60
62
|
- app/javascript/ruby_native/bridge/search_controller.js
|
|
61
63
|
- app/javascript/ruby_native/bridge/tabs_controller.js
|
|
64
|
+
- app/views/ruby_native/auth/start/show.html.erb
|
|
62
65
|
- config/importmap.rb
|
|
63
66
|
- config/routes.rb
|
|
64
67
|
- exe/ruby_native
|
|
@@ -71,6 +74,7 @@ files:
|
|
|
71
74
|
- lib/ruby_native/engine.rb
|
|
72
75
|
- lib/ruby_native/helper.rb
|
|
73
76
|
- lib/ruby_native/native_detection.rb
|
|
77
|
+
- lib/ruby_native/oauth_middleware.rb
|
|
74
78
|
- lib/ruby_native/version.rb
|
|
75
79
|
homepage: https://github.com/Ruby-Native/gem
|
|
76
80
|
licenses:
|