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 +27 -0
- data/salted_login_generator.rb +52 -0
- data/templates/README +141 -0
- data/templates/app-config-development.yml +6 -0
- data/templates/app-config-production.yml +6 -0
- data/templates/app-config-test.yml +6 -0
- data/templates/controller.rb +117 -0
- data/templates/controller_test.rb +174 -0
- data/templates/helper.rb +2 -0
- data/templates/login_system.rb +87 -0
- data/templates/notify.rb +44 -0
- data/templates/notify_change_password.rhtml +10 -0
- data/templates/notify_forgot_password.rhtml +12 -0
- data/templates/notify_signup.rhtml +12 -0
- data/templates/user.rb +56 -0
- data/templates/user_model.sql +15 -0
- data/templates/user_test.rb +90 -0
- data/templates/users.yml +25 -0
- data/templates/view_change_password.rhtml +35 -0
- data/templates/view_forgot_password.rhtml +32 -0
- data/templates/view_login.rhtml +31 -0
- data/templates/view_logout.rhtml +10 -0
- data/templates/view_signup.rhtml +27 -0
- data/templates/view_welcome.rhtml +13 -0
- metadata +71 -0
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,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
|
data/templates/helper.rb
ADDED
@@ -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
|
data/templates/notify.rb
ADDED
@@ -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
|
data/templates/users.yml
ADDED
@@ -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,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 "« 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:
|