sinatra-authentication-nedludd 0.0.1
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/.gitignore +4 -0
- data/History.txt +4 -0
- data/Manifest +26 -0
- data/Rakefile +36 -0
- data/TODO +53 -0
- data/example/dm_extend_app.rb +26 -0
- data/example/dm_sinbook.rb +56 -0
- data/example/extend_views/edit.haml +42 -0
- data/example/extend_views/index.haml +31 -0
- data/example/extend_views/login.haml +21 -0
- data/example/extend_views/show.haml +9 -0
- data/example/extend_views/signup.haml +30 -0
- data/example/mm_app.rb +22 -0
- data/example/tc_app.rb +16 -0
- data/example/tc_sinbook.rb +62 -0
- data/lib/models/abstract_user.rb +44 -0
- data/lib/models/datamapper_user.rb +39 -0
- data/lib/models/dm_adapter.rb +50 -0
- data/lib/sinatra-authentication.rb +227 -0
- data/lib/views/edit.haml +38 -0
- data/lib/views/index.haml +27 -0
- data/lib/views/login.haml +18 -0
- data/lib/views/show.haml +7 -0
- data/lib/views/signup.haml +21 -0
- data/readme.markdown +161 -0
- data/sinatra-authentication-nedludd.gemspec +97 -0
- data/test/datamapper_test.rb +5 -0
- data/test/lib/dm_app.rb +20 -0
- data/test/lib/dm_extend_app.rb +27 -0
- data/test/lib/extend_views/edit.haml +42 -0
- data/test/lib/extend_views/index.haml +31 -0
- data/test/lib/extend_views/login.haml +21 -0
- data/test/lib/extend_views/show.haml +9 -0
- data/test/lib/extend_views/signup.haml +29 -0
- data/test/lib/helper.rb +9 -0
- data/test/route_tests.rb +29 -0
- metadata +190 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
if Object.const_defined?("DataMapper")
|
2
|
+
#require 'dm-core'
|
3
|
+
require 'dm-timestamps'
|
4
|
+
require 'dm-validations'
|
5
|
+
require Pathname(__FILE__).dirname.expand_path + "datamapper_user.rb"
|
6
|
+
require Pathname(__FILE__).dirname.expand_path + "dm_adapter.rb"
|
7
|
+
end
|
8
|
+
|
9
|
+
class User
|
10
|
+
if Object.const_defined?("DataMapper")
|
11
|
+
include DmAdapter
|
12
|
+
else
|
13
|
+
throw "you need to require 'dm-core' for sinatra-authentication to work"
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(interfacing_class_instance)
|
17
|
+
@instance = interfacing_class_instance
|
18
|
+
end
|
19
|
+
|
20
|
+
def id
|
21
|
+
@instance.id
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.authenticate(email, pass)
|
25
|
+
current_user = get(:email => email)
|
26
|
+
return nil if current_user.nil?
|
27
|
+
return current_user if User.encrypt(pass, current_user.salt) == current_user.hashed_password
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def self.encrypt(pass, salt)
|
34
|
+
Digest::SHA1.hexdigest(pass+salt)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.random_string(len)
|
38
|
+
#generate a random password consisting of strings and digits
|
39
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
40
|
+
newpass = ""
|
41
|
+
1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
|
42
|
+
return newpass
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class DmUser
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id, Serial
|
5
|
+
property :email, String, :length => (5..40), :unique => true, :format => :email_address
|
6
|
+
property :hashed_password, String
|
7
|
+
property :salt, String
|
8
|
+
property :created_at, DateTime
|
9
|
+
property :permission_level, Integer, :default => 1
|
10
|
+
|
11
|
+
attr_accessor :password, :password_confirmation
|
12
|
+
#protected equievelant? :protected => true doesn't exist in dm 0.10.0
|
13
|
+
#protected :id, :salt
|
14
|
+
#doesn't behave correctly, I'm not even sure why I did this.
|
15
|
+
|
16
|
+
validates_presence_of :password_confirmation, :unless => Proc.new { |t| t.hashed_password }
|
17
|
+
validates_presence_of :password, :unless => Proc.new { |t| t.hashed_password }
|
18
|
+
validates_confirmation_of :password
|
19
|
+
|
20
|
+
def password=(pass)
|
21
|
+
@password = pass
|
22
|
+
self.salt = User.random_string(10) if !self.salt
|
23
|
+
self.hashed_password = User.encrypt(@password, self.salt)
|
24
|
+
end
|
25
|
+
|
26
|
+
def admin?
|
27
|
+
self.permission_level == -1 || self.id == 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def site_admin?
|
31
|
+
self.id == 1
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def method_missing(m, *args)
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DmAdapter
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.class_eval { include DmAdapter::InstanceMethods }
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
#pass all args to this
|
9
|
+
def all(*args)
|
10
|
+
result = DmUser.all(*args)
|
11
|
+
result.collect {|instance| self.new instance}
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(hash)
|
15
|
+
if user = DmUser.first(hash)
|
16
|
+
self.new user
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def set(attributes)
|
23
|
+
user = DmUser.new attributes
|
24
|
+
user.save
|
25
|
+
user
|
26
|
+
end
|
27
|
+
|
28
|
+
def set!(attributes)
|
29
|
+
user = DmUser.new attributes
|
30
|
+
user.save!
|
31
|
+
user
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(pk)
|
35
|
+
user = DmUser.first(:id => pk)
|
36
|
+
user.destroy
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module InstanceMethods
|
41
|
+
def update(attributes)
|
42
|
+
@instance.update attributes
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing(meth, *args, &block)
|
46
|
+
#cool I just found out * on an array turns the array into a list of args for a function
|
47
|
+
@instance.send(meth, *args, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'pathname'
|
3
|
+
require Pathname(__FILE__).dirname.expand_path + "models/abstract_user"
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
module LilAuthentication
|
7
|
+
def self.registered(app)
|
8
|
+
#INVESTIGATE
|
9
|
+
#the possibility of sinatra having an array of view_paths to load from
|
10
|
+
#PROBLEM
|
11
|
+
#sinatra 9.1.1 doesn't have multiple view capability anywhere
|
12
|
+
#so to get around I have to do it totally manually by
|
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/"
|
15
|
+
|
16
|
+
get '/users' do
|
17
|
+
login_required
|
18
|
+
redirect "/" unless current_user.admin?
|
19
|
+
|
20
|
+
@users = User.all
|
21
|
+
if @users != []
|
22
|
+
haml get_view_as_string("index.haml"), :layout => use_layout?
|
23
|
+
else
|
24
|
+
redirect '/signup'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
get '/users/:id' do
|
29
|
+
login_required
|
30
|
+
|
31
|
+
@user = User.get(:id => params[:id])
|
32
|
+
haml get_view_as_string("show.haml"), :layout => use_layout?
|
33
|
+
end
|
34
|
+
|
35
|
+
#convenience for ajax but maybe entirely stupid and unnecesary
|
36
|
+
get '/logged_in' do
|
37
|
+
if session[:user]
|
38
|
+
"true"
|
39
|
+
else
|
40
|
+
"false"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
get '/login' do
|
45
|
+
haml get_view_as_string("login.haml"), :layout => use_layout?
|
46
|
+
end
|
47
|
+
|
48
|
+
post '/login' do
|
49
|
+
if user = User.authenticate(params[:email], params[:password])
|
50
|
+
session[:user] = user.id
|
51
|
+
|
52
|
+
if Rack.const_defined?('Flash')
|
53
|
+
flash[:notice] = "Login successful."
|
54
|
+
end
|
55
|
+
|
56
|
+
if session[:return_to]
|
57
|
+
redirect_url = session[:return_to]
|
58
|
+
session[:return_to] = false
|
59
|
+
redirect redirect_url
|
60
|
+
else
|
61
|
+
redirect '/'
|
62
|
+
end
|
63
|
+
else
|
64
|
+
if Rack.const_defined?('Flash')
|
65
|
+
flash[:notice] = "The email or password you entered is incorrect."
|
66
|
+
end
|
67
|
+
redirect '/login'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
get '/logout' do
|
72
|
+
session[:user] = nil
|
73
|
+
if Rack.const_defined?('Flash')
|
74
|
+
flash[:notice] = "Logout successful."
|
75
|
+
end
|
76
|
+
redirect '/'
|
77
|
+
end
|
78
|
+
|
79
|
+
get '/signup' do
|
80
|
+
haml get_view_as_string("signup.haml"), :layout => use_layout?
|
81
|
+
end
|
82
|
+
|
83
|
+
post '/signup' do
|
84
|
+
@user = User.set(params[:user])
|
85
|
+
if @user && @user.id
|
86
|
+
session[:user] = @user.id
|
87
|
+
if Rack.const_defined?('Flash')
|
88
|
+
flash[:notice] = "Account created."
|
89
|
+
end
|
90
|
+
redirect '/'
|
91
|
+
else
|
92
|
+
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.'
|
94
|
+
end
|
95
|
+
redirect '/signup'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
get '/users/:id/edit' do
|
100
|
+
login_required
|
101
|
+
redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id]
|
102
|
+
@user = User.get(:id => params[:id])
|
103
|
+
haml get_view_as_string("edit.haml"), :layout => use_layout?
|
104
|
+
end
|
105
|
+
|
106
|
+
post '/users/:id/edit' do
|
107
|
+
login_required
|
108
|
+
redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id]
|
109
|
+
|
110
|
+
user = User.get(:id => params[:id])
|
111
|
+
user_attributes = params[:user]
|
112
|
+
if params[:user][:password] == ""
|
113
|
+
user_attributes.delete("password")
|
114
|
+
user_attributes.delete("password_confirmation")
|
115
|
+
end
|
116
|
+
|
117
|
+
if user.update(user_attributes)
|
118
|
+
if Rack.const_defined?('Flash')
|
119
|
+
flash[:notice] = 'Account updated.'
|
120
|
+
end
|
121
|
+
redirect '/'
|
122
|
+
else
|
123
|
+
if Rack.const_defined?('Flash')
|
124
|
+
flash[:notice] = 'Whoops, looks like there were some problems with your updates.'
|
125
|
+
end
|
126
|
+
redirect "/users/#{user.id}/edit"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
get '/users/:id/delete' do
|
131
|
+
login_required
|
132
|
+
redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id]
|
133
|
+
|
134
|
+
if User.delete(params[:id])
|
135
|
+
if Rack.const_defined?('Flash')
|
136
|
+
flash[:notice] = "User deleted."
|
137
|
+
end
|
138
|
+
else
|
139
|
+
if Rack.const_defined?('Flash')
|
140
|
+
flash[:notice] = "Deletion failed."
|
141
|
+
end
|
142
|
+
end
|
143
|
+
redirect '/'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
module Helpers
|
149
|
+
def login_required
|
150
|
+
#not as efficient as checking the session. but this inits the fb_user if they are logged in
|
151
|
+
if current_user.class != GuestUser
|
152
|
+
return true
|
153
|
+
else
|
154
|
+
session[:return_to] = request.fullpath
|
155
|
+
redirect '/login'
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def current_user
|
161
|
+
if session[:user]
|
162
|
+
User.get(:id => session[:user])
|
163
|
+
else
|
164
|
+
GuestUser.new
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def logged_in?
|
169
|
+
!!session[:user]
|
170
|
+
end
|
171
|
+
|
172
|
+
def use_layout?
|
173
|
+
!request.xhr?
|
174
|
+
end
|
175
|
+
|
176
|
+
#BECAUSE sinatra 9.1.1 can't load views from different paths properly
|
177
|
+
def get_view_as_string(filename)
|
178
|
+
view = options.sinatra_authentication_view_path + filename
|
179
|
+
data = ""
|
180
|
+
f = File.open(view, "r")
|
181
|
+
f.each_line do |line|
|
182
|
+
data += line
|
183
|
+
end
|
184
|
+
return data
|
185
|
+
end
|
186
|
+
|
187
|
+
def render_login_logout(html_attributes = {:class => ""})
|
188
|
+
css_classes = html_attributes.delete(:class)
|
189
|
+
parameters = ''
|
190
|
+
html_attributes.each_pair do |attribute, value|
|
191
|
+
parameters += "#{attribute}=\"#{value}\" "
|
192
|
+
end
|
193
|
+
|
194
|
+
result = "<div id='sinatra-authentication-login-logout' >"
|
195
|
+
if logged_in?
|
196
|
+
logout_parameters = html_attributes
|
197
|
+
# a tad janky?
|
198
|
+
logout_parameters.delete(:rel)
|
199
|
+
result += "<a href='/users/#{current_user.id}/edit' class='#{css_classes} sinatra-authentication-edit' #{parameters}>Edit account</a> "
|
200
|
+
result += "<a href='/logout' class='#{css_classes} sinatra-authentication-logout' #{logout_parameters}>Logout</a>"
|
201
|
+
else
|
202
|
+
result += "<a href='/signup' class='#{css_classes} sinatra-authentication-signup' #{parameters}>Signup</a> "
|
203
|
+
result += "<a href='/login' class='#{css_classes} sinatra-authentication-login' #{parameters}>Login</a>"
|
204
|
+
end
|
205
|
+
|
206
|
+
result += "</div>"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
register LilAuthentication
|
211
|
+
end
|
212
|
+
|
213
|
+
class GuestUser
|
214
|
+
def guest?
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
218
|
+
def permission_level
|
219
|
+
0
|
220
|
+
end
|
221
|
+
|
222
|
+
# current_user.admin? returns false. current_user.has_a_baby? returns false.
|
223
|
+
# (which is a bit of an assumption I suppose)
|
224
|
+
def method_missing(m, *args)
|
225
|
+
return false
|
226
|
+
end
|
227
|
+
end
|
data/lib/views/edit.haml
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#sinatra_authentication
|
2
|
+
- if Rack.const_defined?('Flash')
|
3
|
+
#sinatra_authentication_flash= flash[:notice]
|
4
|
+
%h1
|
5
|
+
Edit
|
6
|
+
- if @user.id == current_user.id
|
7
|
+
account
|
8
|
+
- else
|
9
|
+
- if @user.email
|
10
|
+
= @user.email
|
11
|
+
- else
|
12
|
+
account
|
13
|
+
%form{:action => "/users/#{@user.id}/edit", :method => "post"}
|
14
|
+
.field
|
15
|
+
.label
|
16
|
+
%label{:for => "user_email"} Email
|
17
|
+
%input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text", :value => @user.email }
|
18
|
+
.field
|
19
|
+
.label
|
20
|
+
%label{:for => "user_password"} New password
|
21
|
+
%input{ :id => "user_password", :name => "user[password]", :size => 30, :type => "password" }
|
22
|
+
.field
|
23
|
+
.label
|
24
|
+
%label{:for => "user_password_confirmation"} Confirm
|
25
|
+
%input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" }
|
26
|
+
-# don't render permission field if admin and editing yourself so you don't shoot yourself in the foot
|
27
|
+
- if current_user.admin? && current_user.id != @user.id
|
28
|
+
.field
|
29
|
+
.label
|
30
|
+
%label{:for => 'permission_level'} Permission level
|
31
|
+
%select{ :id => "permission_level", :name => "user[permission_level]" }
|
32
|
+
%option{:value => -1, :selected => @user.admin?}
|
33
|
+
Admin
|
34
|
+
%option{:value => 1, :selected => @user.permission_level == 1}
|
35
|
+
Authenticated user
|
36
|
+
.buttons
|
37
|
+
%input{ :value => "Update", :type => "submit" }
|
38
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#sinatra_authentication
|
2
|
+
%h1.page_title Users
|
3
|
+
%table
|
4
|
+
%tr
|
5
|
+
%th
|
6
|
+
- if current_user.admin?
|
7
|
+
%th permission level
|
8
|
+
- @users.each do |user|
|
9
|
+
%tr
|
10
|
+
%td
|
11
|
+
- if user.email
|
12
|
+
= user.email
|
13
|
+
- else
|
14
|
+
"user #{user.id}"
|
15
|
+
- if current_user.admin?
|
16
|
+
%td= user.permission_level
|
17
|
+
%td
|
18
|
+
%a{:href => "/users/#{user.id}"} show
|
19
|
+
- if current_user.admin?
|
20
|
+
%td
|
21
|
+
%a{:href => "/users/#{user.id}/edit"} edit
|
22
|
+
%td
|
23
|
+
-# this doesn't work for tk
|
24
|
+
- if !user.site_admin?
|
25
|
+
%a{:href => "/users/#{user.id}/delete", :onclick => "return confirm('you sure?')"} delete
|
26
|
+
- else
|
27
|
+
site admin
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#sinatra_authentication
|
2
|
+
- if Rack.const_defined?('Flash')
|
3
|
+
#sinatra_authentication_flash= flash[:notice]
|
4
|
+
%h1.page_title Login
|
5
|
+
%form{:action => "/login", :method => "post"}
|
6
|
+
.field
|
7
|
+
.label
|
8
|
+
%label{:for => "user_email'"} Email
|
9
|
+
%input{:id => "user_email", :name => "email", :size => 30, :type => "text"}
|
10
|
+
.field
|
11
|
+
.label
|
12
|
+
%label{:for => "user_password"} Password
|
13
|
+
%input{:id => "user_password", :name => "password", :size => 30, :type => "password"}
|
14
|
+
.buttons
|
15
|
+
%input{:value => "login", :type => "submit"}
|
16
|
+
%a{:href => "/signup", :class => 'sinatra_authentication_link'}
|
17
|
+
Signup
|
18
|
+
|