sinatra-authentication 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +6 -1
- data/lib/models/abstract_user.rb +39 -1
- data/lib/models/datamapper_user.rb +2 -1
- data/lib/models/dm_adapter.rb +15 -4
- data/lib/models/mm_adapter.rb +21 -9
- data/lib/models/mongoid_adapter.rb +67 -0
- data/lib/models/mongoid_user.rb +41 -0
- data/lib/models/mongomapper_user.rb +4 -8
- data/lib/models/rufus_tokyo_user.rb +48 -19
- data/lib/models/sequel_adapter.rb +68 -0
- data/lib/models/sequel_user.rb +53 -0
- data/lib/models/tc_adapter.rb +20 -2
- data/lib/sinatra-authentication.rb +35 -23
- data/lib/sinatra-authentication/models.rb +5 -0
- data/lib/views/edit.haml +1 -1
- data/lib/views/login.haml +3 -3
- data/lib/views/signup.haml +4 -4
- data/readme.markdown +53 -3
- data/sinatra-authentication-0.3.2.gem +0 -0
- data/sinatra-authentication.gemspec +86 -60
- data/spec/run_all_specs.rb +8 -0
- data/spec/unit/dm_model_spec.rb +3 -0
- data/spec/unit/mm_model_spec.rb +3 -0
- data/spec/unit/mongoid_model_spec.rb +3 -0
- data/spec/unit/sequel_model_spec.rb +10 -0
- data/spec/unit/tc_model_spec.rb +3 -0
- data/spec/unit/user_specs.rb +119 -0
- data/test/lib/dm_app.rb +1 -0
- data/test/lib/dm_extend_app.rb +1 -0
- data/test/lib/extend_views/edit.haml +4 -0
- data/test/lib/extend_views/signup.haml +4 -4
- data/test/lib/helper.rb +4 -0
- data/test/lib/mm_app.rb +16 -15
- data/test/lib/mongoid_app.rb +29 -0
- data/test/lib/sequel_app.rb +22 -0
- data/test/lib/tc_app.rb +2 -0
- data/test/mongoid_test.rb +5 -0
- data/test/mongomapper_test.rb +36 -35
- data/test/sequel_test.rb +5 -0
- metadata +40 -24
- data/.gitignore +0 -4
@@ -0,0 +1,68 @@
|
|
1
|
+
module SequelAdapter
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.class_eval { include SequelAdapter::InstanceMethods }
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def all
|
9
|
+
result = SequelUser.order(:created_at.desc).all
|
10
|
+
result.collect {|instance| self.new instance}
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(hash)
|
14
|
+
if user = SequelUser.first(hash)
|
15
|
+
self.new user
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(attributes)
|
22
|
+
user = SequelUser.new attributes
|
23
|
+
if user.valid?
|
24
|
+
user.save
|
25
|
+
#false
|
26
|
+
end
|
27
|
+
|
28
|
+
self.new user
|
29
|
+
end
|
30
|
+
|
31
|
+
def set!(attributes)
|
32
|
+
user = SequelUser.new attributes
|
33
|
+
user.save!
|
34
|
+
self.new user
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete(pk)
|
38
|
+
user = SequelUser.first(:id => pk)
|
39
|
+
user.destroy
|
40
|
+
!user.exists?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module InstanceMethods
|
45
|
+
def errors
|
46
|
+
@instance.errors.full_messages.join(', ')
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid
|
50
|
+
@instance.valid?
|
51
|
+
end
|
52
|
+
|
53
|
+
def update(attributes)
|
54
|
+
@instance.set attributes
|
55
|
+
if @instance.valid?
|
56
|
+
@instance.save
|
57
|
+
true
|
58
|
+
else
|
59
|
+
false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_missing(meth, *args, &block)
|
64
|
+
#cool I just found out * on an array turns the array into a list of args for a function
|
65
|
+
@instance.send(meth, *args, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
unless DB.table_exists? :sequel_users
|
2
|
+
DB.create_table :sequel_users do
|
3
|
+
primary_key :id
|
4
|
+
String :email, :unique => true, :text => true
|
5
|
+
String :hashed_password
|
6
|
+
String :salt
|
7
|
+
DateTime :created_at
|
8
|
+
Integer :permission_level, :default => 1
|
9
|
+
if Sinatra.const_defined?('FacebookObject')
|
10
|
+
String :fb_uid
|
11
|
+
end
|
12
|
+
|
13
|
+
#check{{char_length(email)=>5..40}}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class SequelUser < Sequel::Model
|
18
|
+
attr_writer :password_confirmation
|
19
|
+
plugin :validation_helpers
|
20
|
+
plugin :timestamps, :create => :created_at
|
21
|
+
|
22
|
+
def validate
|
23
|
+
super
|
24
|
+
email_regexp = /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i
|
25
|
+
validates_format email_regexp, :email
|
26
|
+
validates_presence :email
|
27
|
+
validates_unique :email
|
28
|
+
validates_presence :password if new?
|
29
|
+
errors.add :passwords, ' don\'t match' unless @password == @password_confirmation
|
30
|
+
#validate equality?
|
31
|
+
end
|
32
|
+
#TODO validate format of email
|
33
|
+
|
34
|
+
def password=(pass)
|
35
|
+
@password = pass
|
36
|
+
self.salt = User.random_string(10) if !self.salt
|
37
|
+
self.hashed_password = User.encrypt(@password, self.salt)
|
38
|
+
end
|
39
|
+
|
40
|
+
def admin?
|
41
|
+
self.permission_level == -1 || self.id == 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def site_admin?
|
45
|
+
self.id == 1
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def method_missing(m, *args)
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
data/lib/models/tc_adapter.rb
CHANGED
@@ -21,7 +21,11 @@ module TcAdapter
|
|
21
21
|
def get(hash)
|
22
22
|
if hash[:id]
|
23
23
|
pk = hash[:id]
|
24
|
-
|
24
|
+
if pk.length > 0
|
25
|
+
result = TcUser.get(pk)
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
25
29
|
else
|
26
30
|
result = TcUser.query do |q|
|
27
31
|
hash.each do |key, value|
|
@@ -51,7 +55,13 @@ module TcAdapter
|
|
51
55
|
if user == [] #no user
|
52
56
|
self.new TcUser.set(attributes)
|
53
57
|
else
|
54
|
-
|
58
|
+
if attributes['email'].length == 0
|
59
|
+
error = 'You need to provide an email address'
|
60
|
+
else
|
61
|
+
error = 'That email is already taken'
|
62
|
+
end
|
63
|
+
|
64
|
+
TcUser.new(attributes, error)
|
55
65
|
end
|
56
66
|
end
|
57
67
|
|
@@ -70,6 +80,14 @@ module TcAdapter
|
|
70
80
|
@instance.update attributes
|
71
81
|
end
|
72
82
|
|
83
|
+
def errors
|
84
|
+
@instance.errors.join(', ')
|
85
|
+
end
|
86
|
+
|
87
|
+
def valid
|
88
|
+
@instance.errors.length == 0
|
89
|
+
end
|
90
|
+
|
73
91
|
def method_missing(meth, *args, &block)
|
74
92
|
#cool I just found out * on an array turn the array into a list of args for a function
|
75
93
|
@instance.send(meth, *args, &block)
|
@@ -3,7 +3,7 @@ require 'pathname'
|
|
3
3
|
require Pathname(__FILE__).dirname.expand_path + "models/abstract_user"
|
4
4
|
|
5
5
|
module Sinatra
|
6
|
-
module
|
6
|
+
module SinatraAuthentication
|
7
7
|
def self.registered(app)
|
8
8
|
#INVESTIGATE
|
9
9
|
#the possibility of sinatra having an array of view_paths to load from
|
@@ -11,9 +11,9 @@ module Sinatra
|
|
11
11
|
#sinatra 9.1.1 doesn't have multiple view capability anywhere
|
12
12
|
#so to get around I have to do it totally manually by
|
13
13
|
#loading the view from this path into a string and rendering it
|
14
|
-
set :sinatra_authentication_view_path, Pathname(__FILE__).dirname.expand_path + "views/"
|
14
|
+
app.set :sinatra_authentication_view_path, Pathname(__FILE__).dirname.expand_path + "views/"
|
15
15
|
|
16
|
-
get '/users' do
|
16
|
+
app.get '/users' do
|
17
17
|
login_required
|
18
18
|
redirect "/" unless current_user.admin?
|
19
19
|
|
@@ -25,7 +25,7 @@ module Sinatra
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
get '/users/:id' do
|
28
|
+
app.get '/users/:id' do
|
29
29
|
login_required
|
30
30
|
|
31
31
|
@user = User.get(:id => params[:id])
|
@@ -33,7 +33,7 @@ module Sinatra
|
|
33
33
|
end
|
34
34
|
|
35
35
|
#convenience for ajax but maybe entirely stupid and unnecesary
|
36
|
-
get '/logged_in' do
|
36
|
+
app.get '/logged_in' do
|
37
37
|
if session[:user]
|
38
38
|
"true"
|
39
39
|
else
|
@@ -41,11 +41,15 @@ module Sinatra
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
get '/login' do
|
45
|
-
|
44
|
+
app.get '/login' do
|
45
|
+
if session[:user]
|
46
|
+
redirect '/'
|
47
|
+
else
|
48
|
+
haml get_view_as_string("login.haml"), :layout => use_layout?
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
|
-
post '/login' do
|
52
|
+
app.post '/login' do
|
49
53
|
if user = User.authenticate(params[:email], params[:password])
|
50
54
|
session[:user] = user.id
|
51
55
|
|
@@ -68,7 +72,7 @@ module Sinatra
|
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
71
|
-
get '/logout' do
|
75
|
+
app.get '/logout' do
|
72
76
|
session[:user] = nil
|
73
77
|
if Rack.const_defined?('Flash')
|
74
78
|
flash[:notice] = "Logout successful."
|
@@ -76,13 +80,17 @@ module Sinatra
|
|
76
80
|
redirect '/'
|
77
81
|
end
|
78
82
|
|
79
|
-
get '/signup' do
|
80
|
-
|
83
|
+
app.get '/signup' do
|
84
|
+
if session[:user]
|
85
|
+
redirect '/'
|
86
|
+
else
|
87
|
+
haml get_view_as_string("signup.haml"), :layout => use_layout?
|
88
|
+
end
|
81
89
|
end
|
82
90
|
|
83
|
-
post '/signup' do
|
91
|
+
app.post '/signup' do
|
84
92
|
@user = User.set(params[:user])
|
85
|
-
if @user && @user.id
|
93
|
+
if @user.valid && @user.id
|
86
94
|
session[:user] = @user.id
|
87
95
|
if Rack.const_defined?('Flash')
|
88
96
|
flash[:notice] = "Account created."
|
@@ -90,20 +98,20 @@ module Sinatra
|
|
90
98
|
redirect '/'
|
91
99
|
else
|
92
100
|
if Rack.const_defined?('Flash')
|
93
|
-
flash[:notice] =
|
101
|
+
flash[:notice] = "There were some problems creating your account: #{@user.errors}."
|
94
102
|
end
|
95
|
-
redirect '/signup'
|
103
|
+
redirect '/signup?' + hash_to_query_string(params['user'])
|
96
104
|
end
|
97
105
|
end
|
98
106
|
|
99
|
-
get '/users/:id/edit' do
|
107
|
+
app.get '/users/:id/edit' do
|
100
108
|
login_required
|
101
109
|
redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id]
|
102
110
|
@user = User.get(:id => params[:id])
|
103
111
|
haml get_view_as_string("edit.haml"), :layout => use_layout?
|
104
112
|
end
|
105
113
|
|
106
|
-
post '/users/:id/edit' do
|
114
|
+
app.post '/users/:id/edit' do
|
107
115
|
login_required
|
108
116
|
redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id]
|
109
117
|
|
@@ -121,13 +129,13 @@ module Sinatra
|
|
121
129
|
redirect '/'
|
122
130
|
else
|
123
131
|
if Rack.const_defined?('Flash')
|
124
|
-
flash[:notice] =
|
132
|
+
flash[:notice] = "Whoops, looks like there were some problems with your updates: #{user.errors}."
|
125
133
|
end
|
126
|
-
redirect "/users/#{user.id}/edit"
|
134
|
+
redirect "/users/#{user.id}/edit?" + hash_to_query_string(user_attributes)
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
130
|
-
get '/users/:id/delete' do
|
138
|
+
app.get '/users/:id/delete' do
|
131
139
|
login_required
|
132
140
|
redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id]
|
133
141
|
|
@@ -145,7 +153,7 @@ module Sinatra
|
|
145
153
|
|
146
154
|
|
147
155
|
if Sinatra.const_defined?('FacebookObject')
|
148
|
-
get '/connect' do
|
156
|
+
app.get '/connect' do
|
149
157
|
if fb[:user]
|
150
158
|
if current_user.class != GuestUser
|
151
159
|
user = current_user
|
@@ -166,7 +174,7 @@ module Sinatra
|
|
166
174
|
redirect '/'
|
167
175
|
end
|
168
176
|
|
169
|
-
get '/receiver' do
|
177
|
+
app.get '/receiver' do
|
170
178
|
%[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
171
179
|
<html xmlns="http://www.w3.org/1999/xhtml" >
|
172
180
|
<body>
|
@@ -179,6 +187,10 @@ module Sinatra
|
|
179
187
|
end
|
180
188
|
|
181
189
|
module Helpers
|
190
|
+
def hash_to_query_string(hash)
|
191
|
+
hash.collect {|k,v| "#{k}=#{v}"}.join('&')
|
192
|
+
end
|
193
|
+
|
182
194
|
def login_required
|
183
195
|
#not as efficient as checking the session. but this inits the fb_user if they are logged in
|
184
196
|
if current_user.class != GuestUser
|
@@ -270,7 +282,7 @@ module Sinatra
|
|
270
282
|
end
|
271
283
|
end
|
272
284
|
|
273
|
-
register
|
285
|
+
register SinatraAuthentication
|
274
286
|
end
|
275
287
|
|
276
288
|
class GuestUser
|
data/lib/views/edit.haml
CHANGED
data/lib/views/login.haml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#sinatra_authentication
|
2
|
-
- if Rack.const_defined?('Flash')
|
2
|
+
- if Rack.const_defined?('Flash') && flash[:notice]
|
3
3
|
#sinatra_authentication_flash= flash[:notice]
|
4
4
|
%h1.page_title Login
|
5
5
|
%form{:action => "/login", :method => "post"}
|
@@ -12,9 +12,9 @@
|
|
12
12
|
%label{:for => "user_password"} Password
|
13
13
|
%input{:id => "user_password", :name => "password", :size => 30, :type => "password"}
|
14
14
|
.buttons
|
15
|
-
%input{:value => "
|
15
|
+
%input{:value => "Login", :type => "submit"}
|
16
16
|
%a{:href => "/signup", :class => 'sinatra_authentication_link'}
|
17
|
-
|
17
|
+
Sign up
|
18
18
|
- if Sinatra.const_defined?('FacebookObject')
|
19
19
|
.third_party_signup
|
20
20
|
%h3.section_title One click login:
|
data/lib/views/signup.haml
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
#sinatra_authentication
|
2
|
-
- if Rack.const_defined?('Flash')
|
2
|
+
- if Rack.const_defined?('Flash') && flash[:notice]
|
3
3
|
#sinatra_authentication_flash= flash[:notice]
|
4
|
-
%h1.page_title
|
4
|
+
%h1.page_title Sign Up
|
5
5
|
%form{:action => "/signup", :method => "post"}
|
6
6
|
.field
|
7
7
|
.label
|
8
8
|
%label{:for => "user_email"} Email
|
9
|
-
%input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text" }
|
9
|
+
%input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text" , :value => params[:email]}
|
10
10
|
.field
|
11
11
|
.label
|
12
12
|
%label{:for => "user_password"} Password
|
@@ -16,7 +16,7 @@
|
|
16
16
|
%label{:for => "user_password_confirmation"} Confirm Password
|
17
17
|
%input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" }
|
18
18
|
.buttons
|
19
|
-
%input{ :value => "
|
19
|
+
%input{ :value => "Sign up", :type => "submit" }
|
20
20
|
%a{:href => "/login", :class => 'sinatra_authentication_link'}
|
21
21
|
Login
|
22
22
|
- if Sinatra.const_defined?('FacebookObject')
|
data/readme.markdown
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
### A little sinatra gem that implements user authentication, with support for
|
1
|
+
### A little sinatra gem that implements user authentication, with support for Datamapper, Mongomapper, Mongoid, Sequel and Rufus-Tokyo
|
2
2
|
|
3
3
|
## INSTALLATION:
|
4
4
|
|
5
|
-
in your sinatra app simply require either "dm-core",
|
5
|
+
in your sinatra app simply require either "dm-core", 'sequel', 'rufus-tokyo', 'mongoid' or "mongo_mapper", "digest/sha1", 'rack-flash' (if you want flash messages) and then "sinatra-authentication" and turn on session storage
|
6
6
|
with a super secret key, like so:
|
7
7
|
|
8
8
|
require "dm-core"
|
@@ -226,13 +226,63 @@ And then to access/update your newly defined attributes you use the User class:
|
|
226
226
|
user.delete
|
227
227
|
end
|
228
228
|
|
229
|
-
the User class passes additional method calls along to the interfacing database class, so calls to Datamapper/Mongomapper/RufusTokyo functions should work as expected.
|
229
|
+
the User class passes additional method calls along to the interfacing database class, so most calls to Datamapper/Sequel/Mongomapper/RufusTokyo functions should work as expected.
|
230
|
+
|
231
|
+
If you need to get associations on current_user from the underlying ORM use current_user.db_instance, take this case for example:
|
232
|
+
class Brain
|
233
|
+
include DataMapper::Resource
|
234
|
+
property :type, String
|
235
|
+
property :iq, Integer
|
236
|
+
end
|
237
|
+
|
238
|
+
class DmUser
|
239
|
+
has n, :brains
|
240
|
+
end
|
241
|
+
|
242
|
+
get '/' do
|
243
|
+
@user_brains = current_user.db_instance.brains
|
244
|
+
end
|
230
245
|
|
231
246
|
The database user classes are named as follows:
|
232
247
|
|
233
248
|
* for Datamapper:
|
234
249
|
> DmUser
|
250
|
+
* for Sequel:
|
251
|
+
> SequelUser
|
235
252
|
* for Rufus Tokyo:
|
236
253
|
> TcUser
|
254
|
+
* for Mongoid:
|
255
|
+
> MongoidUser
|
237
256
|
* for Mongomapper:
|
238
257
|
> MmUser
|
258
|
+
|
259
|
+
## Deprecations
|
260
|
+
* All database adapters now store created_at as a Time object.
|
261
|
+
|
262
|
+
## Known issues
|
263
|
+
* First user in database is not properly recognized as site admin
|
264
|
+
> Proposed fix: add site_admin_email option when initialization functionality is added
|
265
|
+
|
266
|
+
## Roadmap
|
267
|
+
|
268
|
+
* Move database adapter initialization, along with auto configuration of sinbook and rack flash functionality into a Sinatra::SinatraAuthentication.init(args) method
|
269
|
+
* Refactor/redesign database adapter interface, make User class AbstractUser and all ORM user classes User, with corresponding specs
|
270
|
+
* Remove Facebook connect support and add support for Omniauth
|
271
|
+
* Provide a method for overriding specific views, and/or specifying your own form partial, (passed an instance of User)
|
272
|
+
* Add Remember me (forever) checkbox to login form
|
273
|
+
* Add next url parameter support for login/signup
|
274
|
+
* Add verb selection on configuration (Sign in / Log in)
|
275
|
+
* Provide optional support through init method for inclusion of username
|
276
|
+
> Where login form accepts either email or username (through the same field)
|
277
|
+
* Add email functionality
|
278
|
+
> Confirmation emails
|
279
|
+
> Forgotten password emails
|
280
|
+
* Look into what might be neccesary to allow for logging in using Ajax
|
281
|
+
|
282
|
+
## Maybe
|
283
|
+
|
284
|
+
* Allow passing custom database attributes into init method, also dynamically altering corresponding signup and user edit views. (potentially leaky abstraction)
|
285
|
+
> As an alternative, create a generic interface for accessing database row names through the various ORMs.
|
286
|
+
> So when users alter their User schemas, I can make my views 'Just Work'.
|
287
|
+
* Add HTTP basic auth support
|
288
|
+
* Add pluggable OAuth consumer/provider support
|