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.
Files changed (41) hide show
  1. data/Rakefile +6 -1
  2. data/lib/models/abstract_user.rb +39 -1
  3. data/lib/models/datamapper_user.rb +2 -1
  4. data/lib/models/dm_adapter.rb +15 -4
  5. data/lib/models/mm_adapter.rb +21 -9
  6. data/lib/models/mongoid_adapter.rb +67 -0
  7. data/lib/models/mongoid_user.rb +41 -0
  8. data/lib/models/mongomapper_user.rb +4 -8
  9. data/lib/models/rufus_tokyo_user.rb +48 -19
  10. data/lib/models/sequel_adapter.rb +68 -0
  11. data/lib/models/sequel_user.rb +53 -0
  12. data/lib/models/tc_adapter.rb +20 -2
  13. data/lib/sinatra-authentication.rb +35 -23
  14. data/lib/sinatra-authentication/models.rb +5 -0
  15. data/lib/views/edit.haml +1 -1
  16. data/lib/views/login.haml +3 -3
  17. data/lib/views/signup.haml +4 -4
  18. data/readme.markdown +53 -3
  19. data/sinatra-authentication-0.3.2.gem +0 -0
  20. data/sinatra-authentication.gemspec +86 -60
  21. data/spec/run_all_specs.rb +8 -0
  22. data/spec/unit/dm_model_spec.rb +3 -0
  23. data/spec/unit/mm_model_spec.rb +3 -0
  24. data/spec/unit/mongoid_model_spec.rb +3 -0
  25. data/spec/unit/sequel_model_spec.rb +10 -0
  26. data/spec/unit/tc_model_spec.rb +3 -0
  27. data/spec/unit/user_specs.rb +119 -0
  28. data/test/lib/dm_app.rb +1 -0
  29. data/test/lib/dm_extend_app.rb +1 -0
  30. data/test/lib/extend_views/edit.haml +4 -0
  31. data/test/lib/extend_views/signup.haml +4 -4
  32. data/test/lib/helper.rb +4 -0
  33. data/test/lib/mm_app.rb +16 -15
  34. data/test/lib/mongoid_app.rb +29 -0
  35. data/test/lib/sequel_app.rb +22 -0
  36. data/test/lib/tc_app.rb +2 -0
  37. data/test/mongoid_test.rb +5 -0
  38. data/test/mongomapper_test.rb +36 -35
  39. data/test/sequel_test.rb +5 -0
  40. metadata +40 -24
  41. 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
@@ -21,7 +21,11 @@ module TcAdapter
21
21
  def get(hash)
22
22
  if hash[:id]
23
23
  pk = hash[:id]
24
- result = TcUser.get(pk)
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
- false
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 LilAuthentication
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
- haml get_view_as_string("login.haml"), :layout => use_layout?
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
- haml get_view_as_string("signup.haml"), :layout => use_layout?
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] = 'There were some problems creating your account. Please be sure you\'ve entered all your information correctly.'
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] = 'Whoops, looks like there were some problems with your updates.'
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 LilAuthentication
285
+ register SinatraAuthentication
274
286
  end
275
287
 
276
288
  class GuestUser
@@ -0,0 +1,5 @@
1
+ # Allows you to use the models from cron scripts etc
2
+ if !Object.const_defined?("Sinatra")
3
+ class Sinatra;end # To please the testing for Sinatra.const_defined?('FacebookObject')
4
+ require File.expand_path(File.join(__FILE__,'../../models/') + 'abstract_user.rb')
5
+ end
@@ -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
5
5
  Edit
@@ -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 => "login", :type => "submit"}
15
+ %input{:value => "Login", :type => "submit"}
16
16
  %a{:href => "/signup", :class => 'sinatra_authentication_link'}
17
- Signup
17
+ Sign up
18
18
  - if Sinatra.const_defined?('FacebookObject')
19
19
  .third_party_signup
20
20
  %h3.section_title One click login:
@@ -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 Signup
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 => "Create account", :type => "submit" }
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')
@@ -1,8 +1,8 @@
1
- ### A little sinatra gem that implements user authentication, with support for datamapper, mongomapper and rufus-tokyo
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", "rufus-tokyo" or "mongo_mapper", "digest/sha1", 'rack-flash' (if you want flash messages) and then "sinatra-authentication" and turn on session storage
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