Skip to content

Commit 4ece28e

Browse files
author
Michel Benevento
committed
add omniauth & csrf support
1 parent 32e6aa6 commit 4ece28e

7 files changed

+121
-95
lines changed

Gemfile.lock

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ PATH
44
roda-auth (0.1.0)
55
bcrypt (~> 3)
66
omniauth (~> 1)
7+
rack_csrf (~> 2.5)
78
roda (~> 2)
89
warden (~> 1.2)
910

@@ -44,6 +45,8 @@ GEM
4445
rack (1.6.0)
4546
rack-test (0.6.2)
4647
rack (>= 1.0)
48+
rack_csrf (2.5.0)
49+
rack (>= 1.1.0)
4750
rake (10.4.2)
4851
roda (2.3.0)
4952
rack

lib/roda/plugins/auth.rb

+94-81
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,32 @@
44
require 'warden'
55
require 'roda'
66
require 'omniauth'
7-
require 'omniauth-twitter'
8-
require 'omniauth-facebook'
97

108
class Roda
11-
9+
1210
module RodaPlugins
1311

1412
module Auth
15-
13+
1614
def self.load_dependencies(app, *args, &block)
1715
app.plugin :drop_body
1816
app.plugin :environments
1917
Warden::Strategies.add(:token, Strategies::Token)
2018
Warden::Strategies.add(:password, Strategies::Password)
2119
Warden::Strategies.add(:basic, Strategies::Basic)
2220
end
23-
21+
2422
def self.configure(app, *args)
2523
options = args.last.is_a?(Hash) ? args.pop : {}
26-
user_class = options.delete(:user_class) || ::User
2724
type = args[0] || :basic
28-
redirect = options.delete(:redirect) || 'login'
29-
cookie = options.delete(:cookie) || {secret:'secr3t'}
25+
user_class = options.delete(:user_class) || ::User
26+
redirect = options.delete(:redirect) || '/login'
3027
case type
3128
when :basic
3229
strategies = [:basic]
3330
when :form
3431
strategies = [:password]
35-
app.use Rack::Session::Cookie, cookie
36-
Warden::Manager.serialize_into_session do |user|
37-
user.id
38-
end
39-
Warden::Manager.serialize_from_session do |id|
40-
user_class.find_by_id(id)
41-
end
42-
app.use OmniAuth::Builder do
43-
provider :developer unless app.production?
44-
provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
45-
provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET']
46-
end
4732
when :token
48-
app.use Rack::Session::Cookie, cookie
4933
strategies = [:token, :password]
5034
end
5135
app.use Warden::Manager do |config|
@@ -58,18 +42,12 @@ def self.configure(app, *args)
5842
:action => redirect
5943
)
6044
end
61-
if providers = options.delete(:omniauth)
62-
app.use OmniAuth::Builder do
63-
provider :developer unless app.production?
64-
providers.each do |name|
65-
key = ENV["API_#{name.to_s.upcase}_KEY"]
66-
secret = ENV["API_#{name.to_s.upcase}_SECRET"]
67-
provider name, key, secret
68-
end
69-
end
45+
if strategies.include? :password
46+
setup_cookie(app, user_class, options)
47+
setup_omniauth(app, options)
7048
end
7149
end
72-
50+
7351
def self.fail(type)
7452
auth_fail = case type
7553
when :basic
@@ -81,157 +59,192 @@ def self.fail(type)
8159
end
8260
->(env) { auth_fail.call(env) }
8361
end
84-
62+
63+
def self.setup_cookie(app, user_class, options)
64+
cookie = options.delete(:cookie) || {secret:'secr3t'}
65+
app.use Rack::Session::Cookie, cookie
66+
Warden::Manager.serialize_into_session do |user|
67+
user.id
68+
end
69+
Warden::Manager.serialize_from_session do |id|
70+
user_class.find_by_id(id)
71+
end
72+
app.plugin :csrf, raise: true, skip_if: lambda { |request|
73+
request.env.key? 'HTTP_AUTHORIZATION'
74+
}
75+
end
76+
77+
def self.setup_omniauth(app, options)
78+
if providers = options.delete(:omniauth)
79+
app.use OmniAuth::Builder do
80+
provider :developer unless app.production?
81+
providers.each do |name|
82+
key = ENV["API_#{name.to_s.upcase}_KEY"]
83+
secret = ENV["API_#{name.to_s.upcase}_SECRET"]
84+
provider name, key, secret
85+
end
86+
end
87+
end
88+
end
89+
8590
module InstanceMethods
86-
91+
8792
def authenticate!
93+
return current_user if current_user
8894
user = warden.authenticate!
89-
warden.set_user(user)
95+
set_user(user)
96+
user
9097
end
9198

99+
def unauthenticate!
100+
#lifted from devise
101+
warden.raw_session.inspect # Without this inspect here. The session does not clear.
102+
warden.logout(scope)
103+
warden.clear_strategies_cache!(scope: scope)
104+
end
105+
92106
def current_user
93107
warden.user
94108
end
95-
96-
def sign_in &block
97-
user = warden.authenticate!
109+
110+
def set_user(user)
98111
warden.set_user(user)
112+
end
113+
114+
def sign_in &block
115+
user = authenticate!
99116
request.is(&block) if block
100117
user
101118
end
102-
119+
103120
def sign_out &block
104-
#lifted from devise
105-
warden.raw_session.inspect # Without this inspect here. The session does not clear.
106-
warden.logout(scope)
107-
warden.clear_strategies_cache!(scope: scope)
108-
121+
unauthenticate!
109122
request.response.status = 204
110123
request.is(&block) if block
111124
end
112-
125+
113126
private
114-
127+
115128
def scope
116129
:user
117130
end
118-
131+
119132
def warden
120133
request.env['warden']
121134
end
122-
135+
123136
def session_path
124137
roda_class.opts[:session_path].to_s
125138
end
126-
139+
127140
end
128-
141+
129142
end
130-
143+
131144
module Strategies
132-
145+
133146
class Base < Warden::Strategies::Base
134-
147+
135148
def success!(user)
136149
user.authentic! if user.respond_to?(:authentic!)
137150
super
138151
end
139-
152+
140153
def authenticate!
141154
user = warden.config[:user_class].authentic?(credentials)
142155
user.nil? ? fail!("Could not log in") : success!(user)
143156
end
144-
157+
145158
private
146-
159+
147160
def warden
148161
@env['warden']
149162
end
150-
163+
151164
def credentials_from_basic
152165
header = authorization_header
153166
return unless header && header =~ /\ABasic (.*)/m
154167
username, password = Base64.decode64($1).split(/:/, 2)
155168
return unless username and password
156169
{ 'username' => username, 'password' => password }
157170
end
158-
171+
159172
def credentials_from_form
160173
request.media_type == "application/x-www-form-urlencoded" && params
161174
end
162-
175+
163176
def credentials_from_body
164177
if request.body
165178
body = request.body.read
166179
!body.empty? && JSON.parse(body)
167180
end
168181
end
169-
182+
170183
def token_from_auth_header
171184
return unless header = authorization_header
172185
match = header =~ /\AAuth (.*)/m
173186
match && { 'token' => $1 }
174187
end
175-
188+
176189
def authorization_header
177190
@env['HTTP_AUTHORIZATION'] || @env['X-HTTP_AUTHORIZATION'] || @env['X_HTTP_AUTHORIZATION'] || @env['REDIRECT_X_HTTP_AUTHORIZATION']
178191
end
179-
192+
180193
end
181194

182-
195+
183196
class Password < Base
184-
197+
185198
def valid?
186199
credentials['username'] && credentials['password']
187200
end
188-
201+
189202
private
190-
203+
191204
def credentials
192205
@credentials ||= credentials_from_form || credentials_from_body || {}
193206
end
194-
207+
195208
end
196-
209+
197210
class Basic < Password
198-
211+
199212
def valid?
200213
credentials['username'] && credentials['password']
201214
end
202-
215+
203216
# def result
204217
# :redirect
205218
# end
206-
219+
207220
private
208-
221+
209222
def credentials
210223
@credentials ||= credentials_from_basic || {}
211224
end
212-
225+
213226
end
214227

215-
228+
216229
class Token < Base
217-
230+
218231
def valid?
219232
credentials['token']
220233
end
221-
234+
222235
private
223-
236+
224237
def credentials
225238
@credentials ||= token_from_auth_header || {}
226239
end
227-
240+
228241
end
229-
242+
230243
end
231-
244+
232245
register_plugin(:auth, Auth)
233-
246+
234247
end
235-
248+
236249

237250
end

roda-auth.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
1414
s.add_runtime_dependency 'warden', '~> 1.2'
1515
s.add_runtime_dependency 'bcrypt', '~> 3'
1616
s.add_runtime_dependency 'omniauth', '~> 1'
17+
s.add_runtime_dependency 'rack_csrf', '~> 2.5'
1718

1819
s.add_development_dependency 'rake', '~> 10'
1920
s.add_development_dependency 'minitest', '~> 5.5'

test/auth_form_test.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def test_private_refuse_redirect
3939
end
4040

4141
def test_private_accepted
42-
post('/logout')
42+
req('/logout')
4343
cookie = login
4444
assert_equal 200, status('/private', {'HTTP_COOKIE' => cookie})
4545
end

0 commit comments

Comments
 (0)