sinatra-doorman 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Nick Plante
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ = Sinatra Doorman
2
+
3
+ A user authentication extension for Sinatra based on Warden.
4
+
5
+ == Features
6
+
7
+ Base
8
+ * Signup w/ email confirmation
9
+ * Login/Logout
10
+
11
+ Optional
12
+ * Remember Me
13
+ * Forgotten password reset
14
+
15
+ == Installation
16
+
17
+ This project requires Sinatra 1.0 ({see #297}[https://sinatra.lighthouseapp.com/projects/9779/tickets/297-sinatra-extension-routes-are-not-available-in-app]).
18
+ Currently the user model requires DataMapper, this dependency may be removed
19
+ in the future.
20
+
21
+ gem install sinatra --pre
22
+ gem install warden pony dm-core dm-validations dm-timestamps
23
+ gem install sinatra-doorman
24
+
25
+ == Usage
26
+
27
+ require 'doorman'
28
+
29
+ use Rack::Session::Cookie
30
+
31
+ #Optional, if you want user notices
32
+ require 'rack/flash'
33
+ use Rack::Flash
34
+
35
+ To use as a middleware
36
+
37
+ use Sinatra::Doorman::Middleware
38
+
39
+ To use as a Sinatra extension, call register on the features you want
40
+
41
+ #call Sinatra.register if you are writing a top-level app
42
+ register Sinatra::Doorman::Base
43
+ register Sinatra::Doorman::RememberMe
44
+ register Sinatra::Doorman::ForgotPassword
45
+
46
+ Note: usually you don't need to call register explicitly when extending 'classic'
47
+ top-level Sinatra apps, because the extension author will call it for you.
48
+ This is not the case in this project, because I wanted to keep some components
49
+ optional, and I did not want to alter the top-level namespace of any Sinatra
50
+ apps using this as middleware.
51
+
52
+ == Views
53
+
54
+ At this time, you need to copy the contents of the views folder in this project
55
+ to the views folder of your Sinatra application.
56
+
57
+ It is my objective to make this middleware useful for any Rack based application.
58
+ One approach that I have been considering is requesting an empty layout from
59
+ the downstream app, and transforming it using {Effigy}[http://github.com/jferris/effigy].
60
+
61
+ Ideally I would like:
62
+ * Reasonable default views without copying files from GH or gem
63
+ * User option to replace/customize default views
64
+ * Rendering within the application layout
65
+
66
+ I can not think of any middleware that adds to or significantly alters an app's
67
+ response body. Perhaps this is not an appropriate thing to be doing, but it makes
68
+ sense to me on some level. Ultimately, I would like this code to be something that
69
+ one could plug into their middleware stack and use without too much trouble.
70
+
71
+ == Development and Testing
72
+
73
+ If you want to work on this project, there is one thing to note.
74
+ When I run all the cucumber features at once, I get some kind of issue with Webrat
75
+ where Infinite Redirect errors are raised on the signup feature. From what I can
76
+ tell, there is not actually any infinite redirect problem. I have not got
77
+ to the bottom of this yet, so in the meantime I run cucumber in two bites:
78
+
79
+ cucumber --tags @signup
80
+ cucumber --tags ~@signup
@@ -0,0 +1,98 @@
1
+ task :default => :test
2
+ task :test => [:spec, :cucumber]
3
+
4
+ namespace :db do
5
+ desc 'Auto-migrate the database (destroys data)'
6
+ task :migrate do
7
+ require 'application'
8
+ DataMapper.auto_migrate!
9
+ end
10
+
11
+ desc 'Auto-upgrade the database (preserves data)'
12
+ task :upgrade do
13
+ require 'application'
14
+ DataMapper.auto_upgrade!
15
+ end
16
+ end
17
+
18
+ require 'spec/rake/spectask'
19
+ desc "Run specs"
20
+ Spec::Rake::SpecTask.new do |t|
21
+ t.spec_files = FileList['spec/**/*.rb']
22
+ t.spec_opts = ['-cfs']
23
+ end
24
+
25
+ require 'cucumber/rake/task'
26
+ desc "Run cucumber features"
27
+ Cucumber::Rake::Task.new do |t|
28
+ t.cucumber_opts = '--format pretty'
29
+ end
30
+
31
+ require "rake/gempackagetask"
32
+ require "rake/rdoctask"
33
+
34
+ # This builds the actual gem. For details of what all these options
35
+ # mean, and other ones you can add, check the documentation here:
36
+ #
37
+ # http://rubygems.org/read/chapter/20
38
+ #
39
+ spec = Gem::Specification.new do |s|
40
+
41
+ # Change these as appropriate
42
+ s.name = "sinatra-doorman"
43
+ s.version = "0.1.0"
44
+ s.summary = "A user authentication middleware built with Sinatra and Warden"
45
+ s.author = "John Mendonca"
46
+ s.email = "joaosinho@gmail.com"
47
+ s.homepage = "http://github.com/johnmendonca/sinatra-doorman"
48
+
49
+ s.has_rdoc = true
50
+ s.extra_rdoc_files = %w(README.rdoc)
51
+ s.rdoc_options = %w(--main README.rdoc)
52
+
53
+ # Add any extra files to include in the gem
54
+ s.files = %w(MIT-LICENSE Rakefile README.rdoc) + Dir["views/**/*"] + Dir["lib/**/*"]
55
+ s.require_paths = ["lib"]
56
+
57
+ # If you want to depend on other gems, add them here, along with any
58
+ # relevant versions
59
+ s.add_dependency("sinatra", "~> 1.0.a")
60
+ s.add_dependency("warden", "~> 0.9.0")
61
+ s.add_dependency("pony")
62
+ s.add_dependency("dm-core", "~> 0.10.2")
63
+ s.add_dependency("dm-validations", "~> 0.10.2")
64
+ s.add_dependency("dm-timestamps", "~> 0.10.2")
65
+
66
+ # If your tests use any gems, include them here
67
+ s.add_development_dependency("rspec")
68
+ s.add_development_dependency("cucumber")
69
+ s.add_development_dependency("webrat")
70
+ s.add_development_dependency("rack-test")
71
+ end
72
+
73
+ # This task actually builds the gem. We also regenerate a static
74
+ # .gemspec file, which is useful if something (i.e. GitHub) will
75
+ # be automatically building a gem for this project. If you're not
76
+ # using GitHub, edit as appropriate.
77
+ #
78
+ # To publish your gem online, install the 'gemcutter' gem; Read more
79
+ # about that here: http://gemcutter.org/pages/gem_docs
80
+ Rake::GemPackageTask.new(spec) do |pkg|
81
+ pkg.gem_spec = spec
82
+
83
+ # Generate the gemspec file for github.
84
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
85
+ File.open(file, "w") {|f| f << spec.to_ruby }
86
+ end
87
+
88
+ # Generate documentation
89
+ Rake::RDocTask.new do |rd|
90
+ rd.main = "README.rdoc"
91
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
92
+ rd.rdoc_dir = "rdoc"
93
+ end
94
+
95
+ desc 'Clear out RDoc and generated packages'
96
+ task :clean => [:clobber_rdoc, :clobber_package] do
97
+ rm "#{spec.name}.gemspec"
98
+ end
@@ -0,0 +1,7 @@
1
+ lib = File.dirname(__FILE__)
2
+ $:.unshift(lib) unless $:.include?(lib)
3
+
4
+ require 'rack/contrib/cookies'
5
+ require 'doorman/messages'
6
+ require 'doorman/user'
7
+ require 'doorman/base'
@@ -0,0 +1,271 @@
1
+ require 'sinatra/base'
2
+ require 'warden'
3
+ require 'pony'
4
+
5
+ module Sinatra
6
+ module Doorman
7
+
8
+ class Warden::SessionSerializer
9
+ def serialize(user); user.id; end
10
+ def deserialize(id); User.get(id); end
11
+ end
12
+
13
+ ##
14
+ # Base Features:
15
+ # * Signup with Email Confirmation
16
+ # * Login/Logout
17
+ ##
18
+
19
+ class PasswordStrategy < Warden::Strategies::Base
20
+ def valid?
21
+ params['user'] &&
22
+ params['user']['login'] &&
23
+ params['user']['password']
24
+ end
25
+
26
+ def authenticate!
27
+ user = User.authenticate(
28
+ params['user']['login'],
29
+ params['user']['password'])
30
+
31
+ if user.nil?
32
+ fail!(:login_bad_credentials)
33
+ elsif !user.confirmed
34
+ fail!(:login_not_confirmed)
35
+ else
36
+ success!(user)
37
+ end
38
+ end
39
+ end
40
+
41
+ module Base
42
+ module Helpers
43
+ def authenticated?
44
+ env['warden'].authenticated?
45
+ end
46
+ alias_method :logged_in?, :authenticated?
47
+
48
+ def notify(type, message)
49
+ message = Messages[message] if message.is_a?(Symbol)
50
+ flash[type] = message if defined?(Rack::Flash)
51
+ end
52
+
53
+ def token_link(type, user)
54
+ "http://#{env['HTTP_HOST']}/#{type}/#{user.confirm_token}"
55
+ end
56
+ end
57
+
58
+ def self.registered(app)
59
+ app.helpers Helpers
60
+
61
+ app.use Warden::Manager do |manager|
62
+ manager.failure_app = lambda { |env|
63
+ env['x-rack.flash'][:error] = Messages[:auth_required] if defined?(Rack::Flash)
64
+ [302, { 'Location' => '/login' }, ['']]
65
+ }
66
+ end
67
+
68
+ Warden::Strategies.add(:password, PasswordStrategy)
69
+
70
+ app.get '/signup/?' do
71
+ redirect '/home' if authenticated?
72
+ haml :signup
73
+ end
74
+
75
+ app.post '/signup' do
76
+ redirect '/home' if authenticated?
77
+
78
+ user = User.new(params[:user])
79
+
80
+ unless user.save
81
+ notify :error, user.errors.first
82
+ redirect back
83
+ end
84
+
85
+ notify :success, :signup_success
86
+ notify :success, 'Signed up: ' + user.confirm_token
87
+ Pony.mail(
88
+ :to => user.email,
89
+ :from => "no-reply@#{env['SERVER_NAME']}",
90
+ :body => token_link('confirm', user))
91
+ redirect "/"
92
+ end
93
+
94
+ app.get '/confirm/:token/?' do
95
+ redirect '/home' if authenticated?
96
+
97
+ if params[:token].nil? || params[:token].empty?
98
+ notify :error, :confirm_no_token
99
+ redirect '/'
100
+ end
101
+
102
+ user = User.first(:confirm_token => params[:token])
103
+ if user.nil?
104
+ notify :error, :confirm_no_user
105
+ else
106
+ user.confirm_email!
107
+ notify :success, :confirm_success
108
+ end
109
+ redirect '/login'
110
+ end
111
+
112
+ app.get '/login/?' do
113
+ redirect '/home' if authenticated?
114
+ haml :login
115
+ end
116
+
117
+ app.post '/login' do
118
+ env['warden'].authenticate(:password)
119
+ redirect '/home' if authenticated?
120
+ notify :error, env['warden'].message
121
+ redirect back
122
+ end
123
+
124
+ app.get '/logout/?' do
125
+ env['warden'].logout(:default)
126
+ notify :success, :logout_success
127
+ redirect '/login'
128
+ end
129
+ end
130
+ end
131
+
132
+ ##
133
+ # Remember Me Feature
134
+ ##
135
+
136
+ COOKIE_KEY = "sinatra.doorman.remember"
137
+
138
+ class RememberMeStrategy < Warden::Strategies::Base
139
+ def valid?
140
+ !!env['rack.cookies'][COOKIE_KEY]
141
+ end
142
+
143
+ def authenticate!
144
+ token = env['rack.cookies'][COOKIE_KEY]
145
+ return unless token
146
+ user = User.first(:remember_token => token)
147
+ env['rack.cookies'].delete(COOKIE_KEY) and return if user.nil?
148
+ success!(user)
149
+ end
150
+ end
151
+
152
+ module RememberMe
153
+ def self.registered(app)
154
+ app.use Rack::Cookies
155
+
156
+ Warden::Strategies.add(:remember_me, RememberMeStrategy)
157
+
158
+ app.before do
159
+ env['warden'].authenticate(:remember_me)
160
+ end
161
+
162
+ Warden::Manager.after_authentication do |user, auth, opts|
163
+ if auth.winning_strategy.is_a?(RememberMeStrategy) ||
164
+ (auth.winning_strategy.is_a?(PasswordStrategy) &&
165
+ auth.params['user']['remember_me'])
166
+ user.remember_me! # new token
167
+ auth.env['rack.cookies'][COOKIE_KEY] = {
168
+ :value => user.remember_token,
169
+ :expires => Time.now + 7 * 24 * 3600,
170
+ :path => '/' }
171
+ end
172
+ end
173
+
174
+ Warden::Manager.before_logout do |user, auth, opts|
175
+ user.forget_me! if user
176
+ auth.env['rack.cookies'].delete(COOKIE_KEY)
177
+ end
178
+ end
179
+ end
180
+
181
+ ##
182
+ # Forgot Password Feature
183
+ ##
184
+
185
+ module ForgotPassword
186
+ def self.registered(app)
187
+ Warden::Manager.after_authentication do |user, auth, opts|
188
+ # If the user requested a new password,
189
+ # but then remembers and logs in,
190
+ # then invalidate password reset token
191
+ if auth.winning_strategy.is_a?(PasswordStrategy)
192
+ user.remembered_password!
193
+ end
194
+ end
195
+
196
+ app.get '/forgot/?' do
197
+ redirect '/home' if authenticated?
198
+ haml :forgot
199
+ end
200
+
201
+ app.post '/forgot' do
202
+ redirect '/home' if authenticated?
203
+ redirect '/' unless params['user']
204
+
205
+ user = User.first_by_login(params['user']['login'])
206
+
207
+ if user.nil?
208
+ notify :error, :forgot_no_user
209
+ redirect back
210
+ end
211
+
212
+ user.forgot_password!
213
+ Pony.mail(
214
+ :to => user.email,
215
+ :from => "no-reply@#{env['SERVER_NAME']}",
216
+ :body => token_link('reset', user))
217
+ notify :success, :forgot_success
218
+ redirect '/login'
219
+ end
220
+
221
+ app.get '/reset/:token/?' do
222
+ redirect '/home' if authenticated?
223
+
224
+ if params[:token].nil? || params[:token].empty?
225
+ notify :error, :reset_no_token
226
+ redirect '/'
227
+ end
228
+
229
+ user = User.first(:confirm_token => params[:token])
230
+ if user.nil?
231
+ notify :error, :reset_no_user
232
+ redirect '/login'
233
+ end
234
+
235
+ haml :reset, :locals => { :confirm_token => user.confirm_token }
236
+ end
237
+
238
+ app.post '/reset' do
239
+ redirect '/home' if authenticated?
240
+ redirect '/' unless params['user']
241
+
242
+ user = User.first(:confirm_token => params[:user][:confirm_token])
243
+ if user.nil?
244
+ notify :error, :reset_no_user
245
+ redirect '/login'
246
+ end
247
+
248
+ success = user.reset_password!(
249
+ params['user']['password'],
250
+ params['user']['password_confirmation'])
251
+
252
+ unless success
253
+ notify :error, :reset_unmatched_passwords
254
+ redirect back
255
+ end
256
+
257
+ user.confirm_email!
258
+ env['warden'].set_user(user)
259
+ notify :success, :reset_success
260
+ redirect '/home'
261
+ end
262
+ end
263
+ end
264
+
265
+ class Middleware < Sinatra::Base
266
+ register Base
267
+ register RememberMe
268
+ register ForgotPassword
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,32 @@
1
+ module Sinatra
2
+ module Doorman
3
+ Messages = {
4
+ :auth_required => 'You must be logged in to view this page.',
5
+ :signup_success => 'You have signed up successfully. A confirmation ' +
6
+ 'email has been sent to you.',
7
+ :confirm_no_token => 'Invalid confirmation URL. Please make sure you ' +
8
+ 'have the correct link from the email.',
9
+ :confirm_no_user => 'Invalid confirmation URL. Please make sure you ' +
10
+ 'have the correct link from the email, and are not already confirmed.',
11
+ :confirm_success => 'You have successfully confirmed your account. ' +
12
+ 'Please log in.',
13
+ # Auto login upon confirmation?
14
+ :login_bad_credentials => 'Invalid Login and Password. Please try again.',
15
+ :login_not_confirmed => 'You must confirm your account before you can ' +
16
+ 'log in. Please click the confirmation link sent to you.',
17
+ # Note: resend confirmation link?
18
+ :logout_success => 'You have been logged out.',
19
+ :forgot_no_user => 'There is no user with that Username or Email. ' +
20
+ 'Please try again.',
21
+ :forgot_success => 'An email with instructions to reset your password ' +
22
+ 'has been sent to you.',
23
+ :reset_no_token => 'Invalid reset URL. Please make sure you ' +
24
+ 'have the correct link from the email.',
25
+ :reset_no_user => 'Invalid reset URL. Please make sure you have the ' +
26
+ 'correct link from the email, and have already reset the password.',
27
+ :reset_unmatched_passwords => 'Password and confirmation do not match. ' +
28
+ 'Please try again.',
29
+ :reset_success => 'Your password has been reset.'
30
+ }
31
+ end
32
+ end
@@ -0,0 +1,103 @@
1
+ require 'dm-core'
2
+ require 'dm-validations'
3
+ require 'dm-timestamps'
4
+
5
+ module Sinatra
6
+ module Doorman
7
+ class User
8
+ include DataMapper::Resource
9
+
10
+ property :id, Serial
11
+ property :username, String, :unique => true, :length => 1..23, :format => /^[a-zA-Z0-9\-_]*$/
12
+ property :email, String, :unique => true, :required => true, :format => :email_address
13
+ property :password_hash, String, :accessor => :protected
14
+ property :salt, String, :accessor => :protected
15
+
16
+ property :confirmed, Boolean, :writer => :protected
17
+ property :confirm_token, String, :writer => :protected
18
+ property :remember_token, String, :writer => :protected
19
+
20
+ property :created_at, DateTime
21
+ property :last_login, DateTime
22
+
23
+ attr_accessor :password, :password_confirmation
24
+
25
+ validates_length :password, :min => 4, :if => :new?
26
+ validates_is_confirmed :password
27
+
28
+ before :create do
29
+ if valid?
30
+ self.password_hash = encrypt(password)
31
+ self.confirm_token = new_token
32
+ end
33
+ end
34
+
35
+ def self.first_by_login(login)
36
+ #if login has @ symbol, treat as email address
37
+ column = ( login =~ /@/ ? :email : :username )
38
+ User.first(column => login)
39
+ end
40
+
41
+ def self.authenticate(login, password)
42
+ user = User.first_by_login(login)
43
+ return user if user && user.authenticated?(password)
44
+ return nil
45
+ end
46
+
47
+ def authenticated?(password)
48
+ self.password_hash == encrypt(password)
49
+ end
50
+
51
+ def remember_me!
52
+ self.remember_token = new_token
53
+ save
54
+ end
55
+
56
+ def forget_me!
57
+ self.remember_token = nil
58
+ save
59
+ end
60
+
61
+ def confirm_email!
62
+ self.confirmed = true
63
+ self.confirm_token = nil
64
+ save
65
+ end
66
+
67
+ def forgot_password!
68
+ self.confirm_token = new_token
69
+ save
70
+ end
71
+
72
+ def remembered_password!
73
+ self.confirm_token = nil
74
+ save
75
+ end
76
+
77
+ def reset_password!(new_password, new_password_confirmation)
78
+ self.password = new_password
79
+ self.password_confirmation = new_password_confirmation
80
+ self.password_hash = encrypt(password) if valid?
81
+ save
82
+ end
83
+
84
+ protected
85
+
86
+ def salt
87
+ if @salt.nil? || @salt.empty?
88
+ secret = Digest::SHA1.hexdigest("--#{Time.now.utc}--")
89
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.utc}--#{secret}--")
90
+ end
91
+ @salt
92
+ end
93
+
94
+ def encrypt(string)
95
+ Digest::SHA1.hexdigest("--#{salt}--#{string}--")
96
+ end
97
+
98
+ def new_token
99
+ encrypt("--#{Time.now.utc}--")
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,50 @@
1
+ module Rack
2
+ class Cookies
3
+ class CookieJar < Hash
4
+ def initialize(cookies)
5
+ @set_cookies = {}
6
+ @delete_cookies = {}
7
+ super()
8
+ update(cookies)
9
+ end
10
+
11
+ def [](name)
12
+ super(name.to_s)
13
+ end
14
+
15
+ def []=(key, options)
16
+ unless options.is_a?(Hash)
17
+ options = { :value => options }
18
+ end
19
+
20
+ options[:path] ||= '/'
21
+ @set_cookies[key] = options
22
+ super(key.to_s, options[:value])
23
+ end
24
+
25
+ def delete(key, options = {})
26
+ options[:path] ||= '/'
27
+ @delete_cookies[key] = options
28
+ super(key.to_s)
29
+ end
30
+
31
+ def finish!(resp)
32
+ @set_cookies.each { |k, v| resp.set_cookie(k, v) }
33
+ @delete_cookies.each { |k, v| resp.delete_cookie(k, v) }
34
+ end
35
+ end
36
+
37
+ def initialize(app)
38
+ @app = app
39
+ end
40
+
41
+ def call(env)
42
+ req = Request.new(env)
43
+ env['rack.cookies'] = cookies = CookieJar.new(req.cookies)
44
+ status, headers, body = @app.call(env)
45
+ resp = Response.new(body, status, headers)
46
+ cookies.finish!(resp)
47
+ resp.to_a
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ %form{:action => "/forgot", :id => "forgot", :method => "POST"}
2
+ %label{:for => "user[login]"} Username or Email
3
+ %input{:id => "user[login]", :name => "user[login]", :type => "text"}
4
+ %input{:id => "forgot", :name => "forgot", :type => "submit", :value => "Send Email"}
@@ -0,0 +1,9 @@
1
+ %form{:action => "/login", :id => "login", :method => "POST"}
2
+ %label{:for => "user[login]"} Username or Email
3
+ %input{:id => "user[login]", :name => "user[login]", :type => "text"}
4
+ %label{:for => "user[password]"} Password
5
+ %input{:id => "user[password]", :name => "user[password]", :type => "text"}
6
+ %label{:for => "user[remember_me]"} Remember Me
7
+ %input{:id => "user[remember_me]", :name => "user[remember_me]", :type => "checkbox"}
8
+ %input{:id => "login", :name => "login", :type => "submit", :value => "Login"}
9
+ %p link to signup
@@ -0,0 +1,7 @@
1
+ %form{:action => "/reset", :id => "reset", :method => "POST"}
2
+ %label{:for => "user[password]"} Password
3
+ %input{:id => "user[password]", :name => "user[password]", :type => "text"}
4
+ %label{:for => "user[password_confirmation]"} Confirm Password
5
+ %input{:id => "user[password_confirmation]", :name => "user[password_confirmation]", :type => "text"}
6
+ %input{:id => "user[confirm_token]", :name => "user[confirm_token]", :type => "hidden", :value => confirm_token}
7
+ %input{:id => "reset", :name => "reset", :type => "submit", :value => "Reset Password"}
@@ -0,0 +1,10 @@
1
+ %form{:action => "/signup", :id => "signup", :method => "POST"}
2
+ %label{:for => "user[username]"} Username
3
+ %input{:id => "user[username]", :name => "user[username]", :type => "text"}
4
+ %label{:for => "user[email]"} Email Address
5
+ %input{:id => "user[email]", :name => "user[email]", :type => "text"}
6
+ %label{:for => "user[password]"} Password
7
+ %input{:id => "user[password]", :name => "user[password]", :type => "text"}
8
+ %label{:for => "user[password_confirmation]"} Confirm Password
9
+ %input{:id => "user[password_confirmation]", :name => "user[password_confirmation]", :type => "text"}
10
+ %input{:id => "signup", :name => "signup", :type => "submit", :value => "Sign up!"}
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-doorman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - John Mendonca
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-10 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sinatra
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.a
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: warden
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: pony
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: dm-core
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.10.2
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: dm-validations
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: 0.10.2
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: dm-timestamps
67
+ type: :runtime
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ version: 0.10.2
74
+ version:
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ type: :development
78
+ version_requirement:
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ - !ruby/object:Gem::Dependency
86
+ name: cucumber
87
+ type: :development
88
+ version_requirement:
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ - !ruby/object:Gem::Dependency
96
+ name: webrat
97
+ type: :development
98
+ version_requirement:
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: "0"
104
+ version:
105
+ - !ruby/object:Gem::Dependency
106
+ name: rack-test
107
+ type: :development
108
+ version_requirement:
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ version:
115
+ description:
116
+ email: joaosinho@gmail.com
117
+ executables: []
118
+
119
+ extensions: []
120
+
121
+ extra_rdoc_files:
122
+ - README.rdoc
123
+ files:
124
+ - MIT-LICENSE
125
+ - Rakefile
126
+ - README.rdoc
127
+ - views/signup.haml
128
+ - views/reset.haml
129
+ - views/forgot.haml
130
+ - views/login.haml
131
+ - lib/rack/contrib/cookies.rb
132
+ - lib/doorman.rb
133
+ - lib/doorman/messages.rb
134
+ - lib/doorman/user.rb
135
+ - lib/doorman/base.rb
136
+ has_rdoc: true
137
+ homepage: http://github.com/johnmendonca/sinatra-doorman
138
+ licenses: []
139
+
140
+ post_install_message:
141
+ rdoc_options:
142
+ - --main
143
+ - README.rdoc
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: "0"
151
+ version:
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: "0"
157
+ version:
158
+ requirements: []
159
+
160
+ rubyforge_project:
161
+ rubygems_version: 1.3.5
162
+ signing_key:
163
+ specification_version: 3
164
+ summary: A user authentication middleware built with Sinatra and Warden
165
+ test_files: []
166
+