sinatra-authentication 0.3.2 → 0.4.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.
- 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
|