salted_login_generator 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/USAGE ADDED
@@ -0,0 +1,27 @@
1
+ NAME
2
+ login - creates a functional login system
3
+
4
+ SYNOPSIS
5
+ login [Controller name]
6
+
7
+ Good names are Account Myaccount Security
8
+
9
+ DESCRIPTION
10
+ This generator creates a general purpose login system.
11
+
12
+ Included:
13
+ - a User model which uses MD5 encryption and salted hashes for passwords
14
+ - a Controller with signup, login, welcome and logoff actions
15
+ - a Notifier that integrates with the controller to prevent script based
16
+ account creation (i.e., requires account verification from the registered
17
+ email address) and supports forgotten and changing passwords
18
+ - a mixin which lets you easily add advanced authentication
19
+ features to your abstract base controller
20
+ - a user_model.sql with the minimal sql required to get the model to work.
21
+ - extensive unit and functional test cases to make sure nothing breaks.
22
+
23
+ EXAMPLE
24
+ ./script/generate login Account
25
+
26
+ This will generate an Account controller with login and logout methods.
27
+ The model is always called User
@@ -0,0 +1,52 @@
1
+ class SaltedLoginGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+
5
+ # Login module, controller class, functional test, and helper.
6
+ m.template "login_system.rb", "lib/login_system.rb"
7
+ m.template "controller.rb", File.join("app/controllers", class_path, "#{file_name}_controller.rb")
8
+ m.template "controller_test.rb", File.join("test/functional", class_path, "#{file_name}_controller_test.rb")
9
+ m.template "helper.rb", File.join("app/helpers", class_path, "#{file_name}_helper.rb")
10
+
11
+ # Model class, unit test, fixtures, and example schema.
12
+ m.template "user.rb", "app/models/user.rb"
13
+ m.template "notify.rb", File.join("app/models", "notify.rb")
14
+ m.template "user_test.rb", "test/unit/user_test.rb"
15
+ m.template "users.yml", "test/fixtures/users.yml"
16
+ m.template "user_model.sql", "db/user_model.sql"
17
+ m.template "app-config-development.yml", "config/environments/app-config-development.yml"
18
+ m.template "app-config-production.yml", "config/environments/app-config-production.yml"
19
+ m.template "app-config-test.yml", "config/environments/app-config-test.yml"
20
+
21
+ # Layout and stylesheet.
22
+ m.template "scaffold:layout.rhtml", "app/views/layouts/scaffold.rhtml"
23
+ m.template "scaffold:style.css", "public/stylesheets/scaffold.css"
24
+
25
+ # Views.
26
+ m.directory File.join("app/views", class_path, file_name)
27
+ login_views.each do |action|
28
+ m.template "view_#{action}.rhtml",
29
+ File.join("app/views", class_path, file_name, "#{action}.rhtml")
30
+ end
31
+
32
+ # raise "1: #{class_path} 2: #{file_name}"
33
+ m.directory File.join("app/views", "notify")
34
+ notify_views.each do |action|
35
+ m.template "notify_#{action}.rhtml",
36
+ File.join("app/views", "notify", "#{action}.rhtml")
37
+ end
38
+
39
+ m.template "README", "README_LOGIN"
40
+ end
41
+ end
42
+
43
+ attr_accessor :controller_class_name
44
+
45
+ def login_views
46
+ %w(welcome login logout signup forgot_password change_password)
47
+ end
48
+
49
+ def notify_views
50
+ %w(signup forgot_password change_password)
51
+ end
52
+ end
data/templates/README ADDED
@@ -0,0 +1,141 @@
1
+ == Installation
2
+
3
+ Done generating the login system. but there are still a few things you have to
4
+ do manually. First open your application.rb and add
5
+
6
+ require_dependency "login_system"
7
+
8
+ to the top of the file and include the login system with
9
+
10
+ include LoginSystem
11
+
12
+ The beginning of your ApplicationController.
13
+ It should look something like this :
14
+
15
+ require_dependency "login_system"
16
+
17
+ class ApplicationController < ActionController::Base
18
+ include LoginSystem
19
+ model :user
20
+
21
+ After you have done the modifications the the AbstractController you can import
22
+ the user model into the database. This model is meant as an example and you
23
+ should extend it. If you just want to get things up and running you can find
24
+ some create table syntax in db/user_model.sql.
25
+
26
+ The model :user is required when you are hitting problems to the degree of
27
+ "Session could not be restored becuase not all items in it are known"
28
+
29
+ You also need to addd the following at the end of your config/environment.rb file:
30
+
31
+ require 'yaml'
32
+ CONFIG = YAML::load(File.open("#{RAILS_ROOT}/config/environments/app-config-#{RAILS_ENV}.yml"))
33
+
34
+ Under the 'enviroments' subdirectory, you'll find
35
+ app-config-{development, production, test}.yml files. Edit these as appropriate.
36
+
37
+ == Requirements
38
+
39
+ You need a database table corresponding to the User model.
40
+
41
+ mysql syntax:
42
+ CREATE TABLE users (
43
+ id int(11) NOT NULL auto_increment,
44
+ login varchar(80) default NULL,
45
+ password varchar(40) default NULL,
46
+ firstname varchar(40) default NULL,
47
+ lastname varchar(40) default NULL,
48
+ uuid char(32) default NULL,
49
+ salt char(32) default NULL,
50
+ verified INT default 0,
51
+ PRIMARY KEY (id)
52
+ );
53
+  
54
+ postgres :
55
+ CREATE TABLE "users" (
56
+  "id" SERIAL NOT NULL UNIQUE,
57
+  "login" VARCHAR(80),
58
+  "password" VARCHAR,
59
+  "firstname" VARCHAR(80),
60
+  "lastname" VARCHAR(80),
61
+  "uuid" CHAR(32),
62
+  "salt" CHAR(32),
63
+  "verified" INT DEFAULT 0,
64
+  PRIMARY KEY("id")
65
+ ) WITH OIDS;
66
+
67
+
68
+ sqlite:
69
+ CREATE TABLE 'users' (
70
+ 'id' INTEGER PRIMARY KEY NOT NULL,
71
+ 'user' VARCHAR(80) DEFAULT NULL,
72
+ 'password' VARCHAR(40) DEFAULT NULL,
73
+ 'firstname' VARCHAR(40) DEFAULT NULL,
74
+ 'lastname' VARCHAR(40) DEFAULT NULL,
75
+ 'uuid' CHAR(32) DEFAULT NULL,
76
+ 'salt' CHAR(32) DEFAULT NULL,
77
+ 'verified' INT DEFAULT 0
78
+ );
79
+
80
+ Of course your user model can have any amount of extra fields. This is just a
81
+ starting point
82
+
83
+ == How to use it
84
+
85
+ Now you can go around and happily add "before_filter :login_required" to the
86
+ controllers which you would like to protect.
87
+
88
+ After integrating the login system with your rails application navigate to your
89
+ new controller's signup method. There you can create a new account. After you
90
+ are done you should have a look at your DB. Your freshly created user will be
91
+ there but the password will be a sha1 hashed 40 digit mess. I find this should
92
+ be the minimum of security which every page offering login&password should give
93
+ its customers. Now you can move to one of those controllers which you protected
94
+ with the before_filter :login_required snippet. You will automatically be re-
95
+ directed to your freshly created login controller and you are asked for a
96
+ password. After entering valid account data you will be taken back to the
97
+ controller which you requested earlier. Simple huh?
98
+
99
+ == Tips & Tricks
100
+
101
+ How do I...
102
+
103
+ ... access the user who is currently logged in
104
+
105
+ A: You can get the user object from the session using @session['user']
106
+ Example:
107
+ Welcome <%%= @session['user'].name %>
108
+
109
+ ... restrict access to only a few methods?
110
+
111
+ A: Use before_filters build in scoping.
112
+ Example:
113
+ before_filter :login_required, :only => [:myaccount, :changepassword]
114
+ before_filter :login_required, :except => [:index]
115
+
116
+ ... check if a user is logged-in in my views?
117
+
118
+ A: @session['user'] will tell you. Here is an example helper which you can use to make this more pretty:
119
+ Example:
120
+ def user?
121
+ !@session['user'].nil?
122
+ end
123
+
124
+ ... return a user to the page they came from before logging in?
125
+
126
+ A: The user will be send back to the last url which called the method "store_location"
127
+ Example:
128
+ User was at /articles/show/1, wants to log in.
129
+ in articles_controller.rb, add store_location to the show function and send the user
130
+ to the login form.
131
+ After he logs in he will be send back to /articles/show/1
132
+
133
+
134
+ You can find more help at http://wiki.rubyonrails.com/rails/show/LoginGenerator
135
+
136
+ == Changelog
137
+
138
+ 1.0.5 Bugfix in generator code
139
+ 1.0.2 Updated the readme with more tips&tricks
140
+ 1.0.1 Fixed problem in the readme
141
+ 1.0.0 First gem release
@@ -0,0 +1,6 @@
1
+ email_from:
2
+ admin_email:
3
+ server_env: development
4
+ app_url:
5
+ app_base_uri:
6
+ app_name:
@@ -0,0 +1,6 @@
1
+ email_from:
2
+ admin_email:
3
+ server_env: production
4
+ app_url:
5
+ app_base_uri:
6
+ app_name:
@@ -0,0 +1,6 @@
1
+ email_from:
2
+ admin_email:
3
+ server_env: test
4
+ app_url:
5
+ app_base_uri:
6
+ app_name:
@@ -0,0 +1,117 @@
1
+ class <%= class_name %>Controller < ApplicationController
2
+ model :user
3
+ layout 'scaffold'
4
+
5
+ before_filter :login_required, :only => [:change_password]
6
+
7
+ def login
8
+ case @request.method
9
+ when :post
10
+ @user = User.new(@params['user'])
11
+ if @session['user'] = User.authenticate(@params['user']['login'], @params['user']['password'])
12
+ flash['notice'] = "Login successful"
13
+ @user = nil
14
+ redirect_back_or_default :action => 'welcome'
15
+ else
16
+ @login = @params['user']['login']
17
+ flash['message'] = "Login unsuccessful"
18
+ end
19
+ when :get
20
+ @user = User.new
21
+ end
22
+ end
23
+
24
+ def signup
25
+ case @request.method
26
+ when :post
27
+ @user = User.new(@params['user'])
28
+ begin
29
+ @user.transaction do
30
+ if @user.save
31
+ Notify.deliver_signup(@user, @params['user']['password'])
32
+ flash['notice'] = "Signup successful! Please check your registered email account to verify your account registration and continue with the login."
33
+ @user = nil
34
+ redirect_to :action => 'login'
35
+ end
36
+ end
37
+ rescue
38
+ flash['message'] = "Error creating account: confirmation email not sent"
39
+ end
40
+ when :get
41
+ @user = User.new
42
+ end
43
+ end
44
+
45
+ def logout
46
+ @session['user'] = nil
47
+ redirect_to :action => 'login'
48
+ end
49
+
50
+ def change_password
51
+ case @request.method
52
+ when :post
53
+ @user = @session['user']
54
+ @user.attributes = @params['user']
55
+ begin
56
+ @user.transaction do
57
+ if @user.save
58
+ @user.change_password(@params['user']['password'])
59
+ Notify.deliver_change_password(@user, @params['user']['password'])
60
+ flash['notice'] = "Your updated password has been emailed to #{@user.email}"
61
+ @user = nil
62
+ redirect_back_or_default :action => 'welcome'
63
+ end
64
+ end
65
+ rescue
66
+ flash['message'] = "Your password could not be changed at this time. Please retry."
67
+ end
68
+ when :get
69
+ @user = User.new
70
+ end
71
+ end
72
+
73
+ def forgot_password
74
+ case @request.method
75
+ when :post
76
+ if @params['user']['email'].empty?
77
+ flash['message'] = "Please enter a valid email address"
78
+ else
79
+ @user = User.find_by_email(@params['user']['email'])
80
+ if @user.nil?
81
+ flash['message'] = "We could not find a user with the email address #{@params['user']['email']}"
82
+ else
83
+ @user.password_confirmation = @user.password
84
+ pass = @user.makepass
85
+ begin
86
+ @user.transaction do
87
+ # raise "1: #{@user.password_confirmation} 2: #{@user.password}" # = nil
88
+ if @user.save
89
+ @user.change_password(pass)
90
+ Notify.deliver_forgot_password(@user, pass)
91
+ flash['notice'] = "Your new password has been emailed to #{@params['user']['email']}"
92
+ @user = nil
93
+ redirect_to :action => 'login' unless !@session['user'].nil?
94
+ redirect_back_or_default :action => 'welcome'
95
+ end
96
+ end
97
+ rescue
98
+ flash['notice'] = "Your password could not be emailed to #{@params['user']['email']}"
99
+ # raise
100
+ end
101
+ end
102
+ end
103
+ when :get
104
+ @user = User.new
105
+ end
106
+ end
107
+
108
+ def verify
109
+ user = User.find_by_uuid(@params['id'])
110
+ user.verify
111
+ flash['notice'] = "Account verified!"
112
+ redirect_to :action => 'login'
113
+ end
114
+
115
+ def welcome
116
+ end
117
+ end
@@ -0,0 +1,174 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'account_controller'
3
+
4
+ # Raise errors beyond the default web-based presentation
5
+ class AccountController; def rescue_action(e) raise e end; end
6
+
7
+ class AccountControllerTest < Test::Unit::TestCase
8
+
9
+ fixtures :users
10
+
11
+ def setup
12
+ @controller = AccountController.new
13
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
14
+ @request.host = "localhost"
15
+ end
16
+
17
+ def test_auth_bob
18
+ @request.session['return-to'] = "/bogus/location"
19
+
20
+ post :login, "user" => { "login" => "bob", "password" => "atest" }
21
+ assert_session_has "user"
22
+
23
+ assert_equal @bob, @response.session["user"]
24
+
25
+ assert_redirect_url "/bogus/location"
26
+ end
27
+
28
+ def test_signup
29
+ ActionMailer::Base.deliveries = []
30
+ @request.session['return-to'] = "/bogus/location"
31
+
32
+ post :signup, "user" => { "login" => "newbob", "password" => "newpassword", "password_confirmation" => "newpassword", "email" => "newbob@test.com" }
33
+ assert_session_has_no "user"
34
+
35
+ assert_redirect_url(@controller.url_for(:action => "login"))
36
+ assert_equal 1, ActionMailer::Base.deliveries.size
37
+ mail = ActionMailer::Base.deliveries[0]
38
+ assert_equal "newbob@test.com", mail.to_addrs[0].to_s
39
+ assert_match /login:\s+\w+\n/, mail.encoded
40
+ assert_match /password:\s+\w+\n/, mail.encoded
41
+
42
+ user = User.find_by_email("newbob@test.com")
43
+ assert_not_nil user
44
+ assert_equal 0, user.verified
45
+ post :verify, "id" => user.uuid.to_s
46
+ user = User.find_by_email("newbob@test.com")
47
+ assert_equal 1, user.verified
48
+ assert_redirect_url(@controller.url_for(:action => "login"))
49
+ end
50
+
51
+ def do_change_password(bad)
52
+ ActionMailer::Base.deliveries = []
53
+
54
+ post :login, "user" => { "login" => "bob", "password" => "atest" }
55
+ assert_session_has "user"
56
+
57
+ @request.session['return-to'] = "/bogus/location"
58
+ if not bad
59
+ post :change_password, "user" => { "password" => "changed_password", "password_confirmation" => "changed_password" }
60
+ assert_equal 1, ActionMailer::Base.deliveries.size
61
+ mail = ActionMailer::Base.deliveries[0]
62
+ assert_equal "bob@test.com", mail.to_addrs[0].to_s
63
+ assert_match /login:\s+\w+\n/, mail.encoded
64
+ assert_match /password:\s+\w+\n/, mail.encoded
65
+
66
+ assert_redirect_url "/bogus/location"
67
+ else
68
+ post :change_password, "user" => { "password" => "bad", "password_confirmation" => "bad" }
69
+ assert_invalid_column_on_record "user", "password"
70
+ assert_success
71
+ assert_equal 0, ActionMailer::Base.deliveries.size
72
+ end
73
+
74
+ get :logout
75
+ assert_session_has_no "user"
76
+
77
+ if not bad
78
+ post :login, "user" => { "login" => "bob", "password" => "changed_password" }
79
+ assert_session_has "user"
80
+ post :change_password, "user" => { "password" => "atest", "password_confirmation" => "atest" }
81
+ else
82
+ post :login, "user" => { "login" => "bob", "password" => "atest" }
83
+ assert_session_has "user"
84
+ end
85
+
86
+ get :logout
87
+ end
88
+
89
+ def test_change_password
90
+ do_change_password(false)
91
+ do_change_password(true)
92
+ end
93
+
94
+ def do_forgot_password(bad, logged_in)
95
+ ActionMailer::Base.deliveries = []
96
+
97
+ if logged_in
98
+ post :login, "user" => { "login" => "bob", "password" => "atest" }
99
+ assert_session_has "user"
100
+ end
101
+
102
+ @request.session['return-to'] = "/bogus/location"
103
+ if bad
104
+ post :forgot_password, "user" => { "email" => "bademail@test.com" }
105
+ assert_equal 0, ActionMailer::Base.deliveries.size
106
+ assert_flash_has "message"
107
+ else
108
+ post :forgot_password, "user" => { "email" => "bob@test.com" }
109
+ assert_equal 1, ActionMailer::Base.deliveries.size
110
+ mail = ActionMailer::Base.deliveries[0]
111
+ assert_equal "bob@test.com", mail.to_addrs[0].to_s
112
+ assert_match /login:\s+\w+\n/, mail.encoded
113
+ assert_match /password:\s+\w{8}\n/, mail.encoded
114
+ mail.encoded =~ /password:\s+(\w{8})\n/
115
+ password = $1
116
+ end
117
+
118
+ if logged_in
119
+ assert_redirect_url "/bogus/location"
120
+ else
121
+ if not bad
122
+ assert_redirect_url(@controller.url_for(:action => "login"))
123
+ post :login, "user" => { "login" => "bob", "password" => "#{password}" }
124
+ end
125
+ end
126
+
127
+ if not bad
128
+ post :change_password, "user" => { "password" => "atest", "password_confirmation" => "atest" }
129
+ get :logout
130
+ end
131
+ end
132
+
133
+ def test_forgot_password
134
+ do_forgot_password(false, false)
135
+ do_forgot_password(false, true)
136
+ do_forgot_password(true, false)
137
+ end
138
+
139
+ def test_bad_signup
140
+ @request.session['return-to'] = "/bogus/location"
141
+
142
+ post :signup, "user" => { "login" => "newbob", "password" => "newpassword", "password_confirmation" => "wrong" }
143
+ assert_invalid_column_on_record "user", "password"
144
+ assert_success
145
+
146
+ post :signup, "user" => { "login" => "yo", "password" => "newpassword", "password_confirmation" => "newpassword" }
147
+ assert_invalid_column_on_record "user", "login"
148
+ assert_success
149
+
150
+ post :signup, "user" => { "login" => "yo", "password" => "newpassword", "password_confirmation" => "wrong" }
151
+ assert_invalid_column_on_record "user", ["login", "password"]
152
+ assert_success
153
+ end
154
+
155
+ def test_invalid_login
156
+ post :login, "user" => { "login" => "bob", "password" => "not_correct" }
157
+
158
+ assert_session_has_no "user"
159
+
160
+ assert_flash_has "message"
161
+ assert_template_has "login"
162
+ end
163
+
164
+ def test_login_logoff
165
+
166
+ post :login, "user" => { "login" => "bob", "password" => "atest" }
167
+ assert_session_has "user"
168
+
169
+ get :logout
170
+ assert_session_has_no "user"
171
+
172
+ end
173
+
174
+ end
@@ -0,0 +1,2 @@
1
+ module <%= class_name %>Helper
2
+ end
@@ -0,0 +1,87 @@
1
+ require_dependency "user"
2
+
3
+ module LoginSystem
4
+
5
+ protected
6
+
7
+ # overwrite this if you want to restrict access to only a few actions
8
+ # or if you want to check if the user has the correct rights
9
+ # example:
10
+ #
11
+ # # only allow nonbobs
12
+ # def authorize?(user)
13
+ # user.login != "bob"
14
+ # end
15
+ def authorize?(user)
16
+ true
17
+ end
18
+
19
+ # overwrite this method if you only want to protect certain actions of the controller
20
+ # example:
21
+ #
22
+ # # don't protect the login and the about method
23
+ # def protect?(action)
24
+ # if ['action', 'about'].include?(action)
25
+ # return false
26
+ # else
27
+ # return true
28
+ # end
29
+ # end
30
+ def protect?(action)
31
+ true
32
+ end
33
+
34
+ # login_required filter. add
35
+ #
36
+ # before_filter :login_required
37
+ #
38
+ # if the controller should be under any rights management.
39
+ # for finer access control you can overwrite
40
+ #
41
+ # def authorize?(user)
42
+ #
43
+ def login_required
44
+
45
+ if not protect?(action_name)
46
+ return true
47
+ end
48
+
49
+ if @session['user'] and authorize?(@session['user'])
50
+ return true
51
+ end
52
+
53
+ # store current location so that we can
54
+ # come back after the user logged in
55
+ store_location
56
+
57
+ # call overwriteable reaction to unauthorized access
58
+ access_denied
59
+ return false
60
+ end
61
+
62
+ # overwrite if you want to have special behavior in case the user is not authorized
63
+ # to access the current operation.
64
+ # the default action is to redirect to the login screen
65
+ # example use :
66
+ # a popup window might just close itself for instance
67
+ def access_denied
68
+ redirect_to :controller=>"/<%= file_name %>", :action =>"login"
69
+ end
70
+
71
+ # store current uri in the session.
72
+ # we can return to this location by calling return_location
73
+ def store_location
74
+ @session['return-to'] = @request.request_uri
75
+ end
76
+
77
+ # move to the last store_location call or to the passed default one
78
+ def redirect_back_or_default(default)
79
+ if @session['return-to'].nil?
80
+ redirect_to default
81
+ else
82
+ redirect_to_url @session['return-to']
83
+ @session['return-to'] = nil
84
+ end
85
+ end
86
+
87
+ end
@@ -0,0 +1,44 @@
1
+ class Notify < ActionMailer::Base
2
+ def signup(user, password='<what you entered on the website>', url=nil, sent_on=Time.now)
3
+ # Email header info
4
+ @recipients = "#{user.email}"
5
+ @from = CONFIG['email_from'].to_s
6
+ @subject = "[#{CONFIG['app_name']}] Welcome to #{CONFIG['app_name']}!"
7
+ @sent_on = sent_on
8
+
9
+ # Email body substitutions
10
+ @body["name"] = "#{user.firstname} #{user.lastname}"
11
+ @body["login"] = user.login
12
+ @body["password"] = password
13
+ @body["url"] = url || CONFIG['app_url'].to_s
14
+ @body["uuid"] = user.uuid
15
+ end
16
+
17
+ def forgot_password(user, password, url=nil, sent_on=Time.now)
18
+ # Email header info
19
+ @recipients = "#{user.email}"
20
+ @from = CONFIG['email_from'].to_s
21
+ @subject = "[#{CONFIG['app_name']}] Welcome to #{CONFIG['app_name']}!"
22
+ @sent_on = sent_on
23
+
24
+ # Email body substitutions
25
+ @body["name"] = "#{user.firstname} #{user.lastname}"
26
+ @body["login"] = user.login
27
+ @body["password"] = password
28
+ @body["url"] = url || CONFIG['app_url'].to_s
29
+ end
30
+
31
+ def change_password(user, password, url=nil, sent_on=Time.now)
32
+ # Email header info
33
+ @recipients = "#{user.email}"
34
+ @from = CONFIG['email_from'].to_s
35
+ @subject = "[#{CONFIG['app_name']}] Welcome to #{CONFIG['app_name']}!"
36
+ @sent_on = sent_on
37
+
38
+ # Email body substitutions
39
+ @body["name"] = "#{user.firstname} #{user.lastname}"
40
+ @body["login"] = user.login
41
+ @body["password"] = password
42
+ @body["url"] = url || CONFIG['app_url'].to_s
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ Dear <%%= @name %>,
2
+
3
+ At your request, logbook has changed your password. If it was not at your request, then you should be aware that someone has access to your account and requested this change.
4
+
5
+ Your new login credentials are:
6
+
7
+ login: <%%= @login %>
8
+ password: <%%= @password %>
9
+
10
+ <%%= @url %>
@@ -0,0 +1,12 @@
1
+ Dear <%%= @name %>,
2
+
3
+ At your request, logbook has reset your password. If it was not at your request, then you should be aware that someone has entered your email address as theirs in the forgotten password section of logbook.
4
+
5
+ Your new login credentials are:
6
+
7
+ login: <%%= @login %>
8
+ password: <%%= @password %>
9
+
10
+ It's advisable for you to change your password as soon as you login. It's as simple as navigating to 'Preferences' and clicking on 'Change Password'.
11
+
12
+ <%%= @url %>
@@ -0,0 +1,12 @@
1
+ Welcome to logbook, <%%= @name %>.
2
+
3
+ Your login credentials are:
4
+
5
+ login: <%%= @login %>
6
+ password: <%%= @password %>
7
+
8
+ Please click on the following link to confirm your registration:
9
+
10
+ <a href="<%%= @url%>account/verify/<%%= @uuid %>">Click me!</a>
11
+
12
+ <%%= @url %>
data/templates/user.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'digest/md5'
2
+
3
+ # this model expects a certain database layout and its based on the name/login pattern.
4
+ class User < ActiveRecord::Base
5
+
6
+ def self.authenticate(login, pass)
7
+ u = find_first(["login = ? AND verified = 1", login])
8
+ if u.nil?
9
+ return nil
10
+ end
11
+ find_first(["login = ? AND password = ? AND verified = 1", login, salted_password(u.salt, hashed(pass))])
12
+ end
13
+
14
+ def change_password(pass)
15
+ update_attribute("salt", self.class.hashed("salt-#{Time.now}"))
16
+ update_attribute("password", self.class.salted_password(salt, self.class.hashed(pass)))
17
+ end
18
+
19
+ def makepass
20
+ chars = ("a".."z").to_a + (1..9).to_a
21
+ chars = chars.sort_by { rand }
22
+ s = chars[0..7].to_s
23
+ end
24
+
25
+ def verify
26
+ update_attribute("verified", 1)
27
+ end
28
+
29
+ protected
30
+
31
+ def self.hashed(str)
32
+ return Digest::MD5.hexdigest("change-me--#{str}--")[0..31]
33
+ end
34
+
35
+ before_create :generate_uuid, :crypt_password
36
+
37
+ def crypt_password
38
+ write_attribute("salt", self.class.hashed("salt-#{Time.now}"))
39
+ write_attribute("password", self.class.salted_password(salt, self.class.hashed(password)))
40
+ end
41
+
42
+ def generate_uuid
43
+ self.uuid = self.class.hashed("uuid-#{Time.now}")
44
+ end
45
+
46
+ def self.salted_password(salt, hashed_password)
47
+ hashed(salt + hashed_password)
48
+ end
49
+
50
+ validates_length_of :login, :within => 3..40
51
+ validates_length_of :password, :within => 5..40
52
+ validates_presence_of :login, :password, :password_confirmation
53
+ validates_uniqueness_of :login, :on => :create
54
+ validates_uniqueness_of :email, :on => :create
55
+ validates_confirmation_of :password
56
+ end
@@ -0,0 +1,15 @@
1
+ CREATE TABLE users (
2
+ id int NOT NULL auto_increment,
3
+ login varchar(80) default NULL,
4
+ password varchar(40) default NULL,
5
+ email varchar(60) default NULL,
6
+ firstname varchar(40) default NULL,
7
+ lastname varchar(40) default NULL,
8
+ uuid char(32) default NULL,
9
+ salt char(32) default NULL,
10
+ verified INT default 0,
11
+ created_at DATETIME default NULL,
12
+ updated_at DATETIME default NULL,
13
+ logged_in_at DATETIME default NULL,
14
+ PRIMARY KEY (id)
15
+ );
@@ -0,0 +1,90 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class UserTest < Test::Unit::TestCase
4
+
5
+ fixtures :users
6
+
7
+ def test_auth
8
+
9
+ assert_equal @bob, User.authenticate("bob", "atest")
10
+ assert_nil User.authenticate("nonbob", "atest")
11
+
12
+ end
13
+
14
+
15
+ def test_passwordchange
16
+
17
+ @longbob.change_password("nonbobpasswd")
18
+ assert_equal @longbob, User.authenticate("longbob", "nonbobpasswd")
19
+ assert_nil User.authenticate("longbob", "alongtest")
20
+ @longbob.change_password("alongtest")
21
+ assert_equal @longbob, User.authenticate("longbob", "alongtest")
22
+ assert_nil User.authenticate("longbob", "nonbobpasswd")
23
+
24
+ end
25
+
26
+ def test_disallowed_passwords
27
+
28
+ u = User.new
29
+ u.login = "nonbob"
30
+
31
+ u.password = u.password_confirmation = "tiny"
32
+ assert !u.save
33
+ assert u.errors.invalid?('password')
34
+
35
+ u.password = u.password_confirmation = "hugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehuge"
36
+ assert !u.save
37
+ assert u.errors.invalid?('password')
38
+
39
+ u.password = u.password_confirmation = ""
40
+ assert !u.save
41
+ assert u.errors.invalid?('password')
42
+
43
+ u.password = u.password_confirmation = "bobs_secure_password"
44
+ assert u.save
45
+ assert u.errors.empty?
46
+
47
+ end
48
+
49
+ def test_bad_logins
50
+
51
+ u = User.new
52
+ u.password = u.password_confirmation = "bobs_secure_password"
53
+
54
+ u.login = "x"
55
+ assert !u.save
56
+ assert u.errors.invalid?('login')
57
+
58
+ u.login = "hugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhug"
59
+ assert !u.save
60
+ assert u.errors.invalid?('login')
61
+
62
+ u.login = ""
63
+ assert !u.save
64
+ assert u.errors.invalid?('login')
65
+
66
+ u.login = "okbob"
67
+ assert u.save
68
+ assert u.errors.empty?
69
+
70
+ end
71
+
72
+
73
+ def test_collision
74
+ u = User.new
75
+ u.login = "existingbob"
76
+ u.password = u.password_confirmation = "bobs_secure_password"
77
+ assert !u.save
78
+ end
79
+
80
+
81
+ def test_create
82
+ u = User.new
83
+ u.login = "nonexistingbob"
84
+ u.password = u.password_confirmation = "bobs_secure_password"
85
+
86
+ assert u.save
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,25 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+
3
+ bob:
4
+ id: 1000001
5
+ login: bob
6
+ password: 914a1dc3b25d0e37548826941aa9876a # atest
7
+ salt: 6b03707e55eb8fbecb69b04a28539180
8
+ email: bob@test.com
9
+ verified: 1
10
+
11
+ existingbob:
12
+ id: 1000002
13
+ login: existingbob
14
+ password: 900a99c96ebddd41dc15c87169de1f65 # atest
15
+ salt: e77e00345e8a9e26c77fb9c1f2e690e7
16
+ email: existingbob@test.com
17
+ verified: 1
18
+
19
+ longbob:
20
+ id: 1000003
21
+ login: longbob
22
+ password: bc9ce7077ba7e7e5f5e2ca3efd1e7690 # alongtest
23
+ salt: 5e6f3f50b1232072d1bdad4b49b4cf1f
24
+ email: longbob@test.com
25
+ verified: 1
@@ -0,0 +1,35 @@
1
+ <%%= start_form_tag :action=> "change_password" %>
2
+
3
+ <div title="Change password" class="form">
4
+ <h3>Change Password</h3>
5
+ <%% if @flash['notice'] %>
6
+ <div>
7
+ <p><%%= @flash['notice'] %></p>
8
+ </div>
9
+ <%% end %>
10
+ <%% if @flash['message'] %>
11
+ <div id="ErrorExplanation">
12
+ <h2><%%= @flash['message'] %></h2>
13
+ </div>
14
+ <%% end %>
15
+ <%%= error_messages_for 'user' %><br/>
16
+
17
+ <div class="form-padding">
18
+ <p>
19
+ Enter your new password in the fields below and click 'Change Password'
20
+ to have a new password sent to your email inbox.
21
+ </p>
22
+ <label for="user_password">Choose password:</label><br/>
23
+ <%%= password_field "user", "password", :size => 30, :value => "" %><br/>
24
+ <label for="user_password_confirmation">Confirm password:</label><br/>
25
+ <%%= password_field "user", "password_confirmation", :size => 30, :value => "" %><br/>
26
+
27
+ <div class="button-bar">
28
+ <%%= submit_tag "Change password" %>
29
+ <%%= link_to 'Cancel', :action=> 'login' %>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <%%= end_form_tag %>
35
+
@@ -0,0 +1,32 @@
1
+ <%%= start_form_tag :action=> "forgot_password" %>
2
+
3
+ <div title="Forgotten password" class="form">
4
+ <h3>Forgotten Password</h3>
5
+ <%% if @flash['notice'] %>
6
+ <div>
7
+ <p><%%= @flash['notice'] %></p>
8
+ </div>
9
+ <%% end %>
10
+ <%% if @flash['message'] %>
11
+ <div id="ErrorExplanation">
12
+ <h2><%%= @flash['message'] %></h2>
13
+ </div>
14
+ <%% end %>
15
+
16
+ <div class="form-padding">
17
+ <p>
18
+ Enter your email address in the field below and click 'Reset Password'
19
+ to have a new password sent to your email inbox.
20
+ </p>
21
+ <label for="user_email">Email:</label><br/>
22
+ <%%= text_field "user", "email", :size => 30, :value => "" %><br/>
23
+
24
+ <div class="button-bar">
25
+ <%%= submit_tag "Reset password" %>
26
+ <%%= link_to 'Cancel', :action=> 'login' %>
27
+ </div>
28
+ </div>
29
+ </div>
30
+
31
+ <%%= end_form_tag %>
32
+
@@ -0,0 +1,31 @@
1
+ <%%= start_form_tag :action=> "login" %>
2
+
3
+ <div title="Account login" class="form">
4
+ <h3>Please login</h3>
5
+ <%% if @flash['notice'] %>
6
+ <div>
7
+ <p><%%= @flash['notice'] %></p>
8
+ </div>
9
+ <%% end %>
10
+ <%% if @flash['message'] %>
11
+ <div id="ErrorExplanation">
12
+ <h2><%%= @flash['message'] %></h2>
13
+ </div>
14
+ <%% end %>
15
+
16
+ <div class="form-padding">
17
+ <label for="user_login">Login:</label><br/>
18
+ <%%= text_field "user", "login", :size => 30 %><br/>
19
+ <label for="user_password">Password:</label><br/>
20
+ <%%= password_field "user", "password", :size => 30, :value => "" %><br/>
21
+
22
+ <div class="button-bar">
23
+ <%%= submit_tag "Login" %>
24
+ <%%= link_to 'Register for an account', :action => 'signup' %> |
25
+ <%%= link_to 'Forgot your password?', :action => 'forgot_password' %>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <%%= end_form_tag %>
31
+
@@ -0,0 +1,10 @@
1
+
2
+ <div class="memo">
3
+ <h3>Logoff</h3>
4
+
5
+ <p>You are now logged out of the system...</p>
6
+
7
+ <%%= link_to "&#171; login", :action=>"login"%>
8
+
9
+ </div>
10
+
@@ -0,0 +1,27 @@
1
+ <%%= start_form_tag :action=> "signup" %>
2
+
3
+ <div title="Account signup" class="form">
4
+ <h3>Signup</h3>
5
+ <%% if @flash['message'] %>
6
+ <div id="ErrorExplanation">
7
+ <h2><%%= @flash['message'] %></h2>
8
+ </div>
9
+ <%% end %>
10
+ <%%= error_messages_for 'user' %><br/>
11
+
12
+ <div class="form-padding">
13
+ <label for="user_login">Desired login:</label><br/>
14
+ <%%= text_field "user", "login", :size => 30 %><br/>
15
+ <label for="user_email">Email address:</label><br/>
16
+ <%%= text_field "user", "email", :size => 30 %><br/>
17
+ <label for="user_password">Choose password:</label><br/>
18
+ <%%= password_field "user", "password", :size => 30, :value => "" %><br/>
19
+ <label for="user_password_confirmation">Confirm password:</label><br/>
20
+ <%%= password_field "user", "password_confirmation", :size => 30, :value => "" %><br/>
21
+
22
+ <div class="button-bar">
23
+ <%%= submit_tag "Signup" %>
24
+ </div>
25
+ <div>
26
+ <%%= end_form_tag %>
27
+
@@ -0,0 +1,13 @@
1
+
2
+ <div class="memo">
3
+ <h3>Welcome</h3>
4
+
5
+ <p>You are now logged into the system...</p>
6
+ <p>
7
+ Since you are here it's safe to assume the application never called store_location, otherwise
8
+ you would have been redirected somewhere else after a successful login.
9
+ </p>
10
+
11
+ <%%= link_to "&#171; logout", :action=>"logout"%>
12
+
13
+ </div>
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.8
3
+ specification_version: 1
4
+ name: salted_login_generator
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.1
7
+ date: 2005-04-05
8
+ summary: "[Rails] Login generator with salted passwords."
9
+ require_paths:
10
+ - "."
11
+ email: jhosteny@mac.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description: Generates Rails code implementing a login system for your Rails app.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - Tobias Luetke
29
+ - Joe Hosteny
30
+ files:
31
+ - USAGE
32
+ - salted_login_generator.rb
33
+ - templates/controller.rb
34
+ - templates/controller_test.rb
35
+ - templates/helper.rb
36
+ - templates/login_system.rb
37
+ - templates/README
38
+ - templates/user.rb
39
+ - templates/user_test.rb
40
+ - templates/users.yml
41
+ - templates/view_login.rhtml
42
+ - templates/view_logout.rhtml
43
+ - templates/view_signup.rhtml
44
+ - templates/view_welcome.rhtml
45
+ - templates/user_model.sql
46
+ - templates/notify.rb
47
+ - templates/view_forgot_password.rhtml
48
+ - templates/view_change_password.rhtml
49
+ - templates/notify_signup.rhtml
50
+ - templates/notify_forgot_password.rhtml
51
+ - templates/notify_change_password.rhtml
52
+ - templates/app-config-development.yml
53
+ - templates/app-config-production.yml
54
+ - templates/app-config-test.yml
55
+ test_files: []
56
+ rdoc_options: []
57
+ extra_rdoc_files: []
58
+ executables: []
59
+ extensions: []
60
+ requirements: []
61
+ dependencies:
62
+ - !ruby/object:Gem::Dependency
63
+ name: rails
64
+ version_requirement:
65
+ version_requirements: !ruby/object:Gem::Version::Requirement
66
+ requirements:
67
+ -
68
+ - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 0.10.0
71
+ version: