login_sugar_generator 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ class LoginSugar < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :<%= plural_name %>, :force => true do |t|
4
+ t.column :login, :string, :limit => 80, :null => false
5
+ t.column :salted_password, :string, :limit => 40, :null => false
6
+ t.column :email, :string, :limit => 60, :null => false
7
+ t.column :first_name, :string, :limit => 40
8
+ t.column :last_name, :string, :limit => 40
9
+ t.column :salt, 'CHAR(40)', :null => false
10
+ t.column :verified, :boolean, :default => false
11
+ t.column :role, :string, :limit => 40, :default => nil
12
+ t.column :security_token, 'CHAR(40)', :default => nil
13
+ t.column :token_expiry, :datetime, :default => nil
14
+ t.column :deleted, :boolean, :default => false
15
+ end
16
+ end
17
+
18
+ def self.down
19
+ drop_table :<%= plural_name %>
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+
2
+ class Clock
3
+ @@time = Time.now
4
+ cattr_writer :time
5
+
6
+ def self.now
7
+ @@time
8
+ end
9
+
10
+ def self.advance_by_days( days )
11
+ @@time += (days * 60 *60 * 24)
12
+ end
13
+
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'models/<%= file_name %>_notify.rb'
2
+
3
+ ActionMailer::Base.class_eval {
4
+ @@inject_one_error = false
5
+ cattr_accessor :inject_one_error
6
+
7
+ private
8
+ def perform_delivery_test(mail)
9
+ if inject_one_error
10
+ ActionMailer::Base::inject_one_error = false
11
+ raise "Failed to send email" if raise_delivery_errors
12
+ else
13
+ deliveries << mail
14
+ end
15
+ end
16
+ }
@@ -0,0 +1,50 @@
1
+ class <%= class_name %>Notify < ActionMailer::Base
2
+ def signup(<%= singular_name %>, password, url=nil)
3
+ setup_email(<%= singular_name %>)
4
+
5
+ # Email header info
6
+ @subject += "Welcome to #{<%= class_name %>System::CONFIG[:app_name]}!"
7
+
8
+ # Email body substitutions
9
+ @body["name"] = "#{<%= singular_name %>.first_name} #{<%= singular_name %>.last_name}"
10
+ @body["login"] = <%= singular_name %>.login
11
+ @body["password"] = password
12
+ @body["url"] = url || <%= class_name %>System::CONFIG[:app_url].to_s
13
+ @body["app_name"] = <%= class_name %>System::CONFIG[:app_name].to_s
14
+ end
15
+
16
+ def forgot_password(<%= singular_name %>, url=nil)
17
+ setup_email(<%= singular_name %>)
18
+
19
+ # Email header info
20
+ @subject += "Forgotten password notification"
21
+
22
+ # Email body substitutions
23
+ @body["name"] = "#{<%= singular_name %>.first_name} #{<%= singular_name %>.last_name}"
24
+ @body["login"] = <%= singular_name %>.login
25
+ @body["url"] = url || <%= class_name %>System::CONFIG[:app_url].to_s
26
+ @body["app_name"] = <%= class_name %>System::CONFIG[:app_name].to_s
27
+ end
28
+
29
+ def change_password(<%= singular_name %>, password, url=nil)
30
+ setup_email(<%= singular_name %>)
31
+
32
+ # Email header info
33
+ @subject += "Changed password notification"
34
+
35
+ # Email body substitutions
36
+ @body["name"] = "#{<%= singular_name %>.first_name} #{<%= singular_name %>.last_name}"
37
+ @body["login"] = <%= singular_name %>.login
38
+ @body["password"] = password
39
+ @body["url"] = url || <%= class_name %>System::CONFIG[:app_url].to_s
40
+ @body["app_name"] = <%= class_name %>System::CONFIG[:app_name].to_s
41
+ end
42
+
43
+ def setup_email(<%= singular_name %>)
44
+ @recipients = "#{<%= singular_name %>.email}"
45
+ @from = <%= class_name %>System::CONFIG[:email_from].to_s
46
+ @subject = "[#{<%= class_name %>System::CONFIG[:app_name]}] "
47
+ @sent_on = Time.now
48
+ @headers['Content-Type'] = "text/plain; charset=#{<%= class_name %>System::CONFIG[:mail_charset]}; format=flowed"
49
+ end
50
+ end
@@ -0,0 +1,10 @@
1
+ Dear <%%= @name %>,
2
+
3
+ At your request, <%%= @app_name %> 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,5 @@
1
+ Dear <%%= @name %>,
2
+
3
+ At your request, <%%= @app_name %> has permanently deleted your account.
4
+
5
+ <%%= @url %>
@@ -0,0 +1,11 @@
1
+ Dear <%%= @name %>,
2
+
3
+ At your request, <%%= @app_name %> has sent you the following URL so that you may 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 <%%= @app_name %>.
4
+
5
+ Please click on the following link to go to the change password page:
6
+
7
+ <a href="<%%= @url%>">Click me!</a>
8
+
9
+ It's advisable for you to change your password as soon as you login.
10
+
11
+ <%%= @url %>
@@ -0,0 +1,9 @@
1
+ Dear <%%= @name %>,
2
+
3
+ At your request, <%%= @app_name %> has marked your account for deletion. 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
+ The following link is provided for you to restore your deleted account. If you click on this link within the next <%%= @days %> days, your account will not be deleted. Otherwise, simply ignore this email and your account will be permanently deleted after that time.
6
+
7
+ <a href="<%%= @url%>">Click me!</a>
8
+
9
+ <%%= @url %>
@@ -0,0 +1,12 @@
1
+ Welcome to <%%= @app_name %>, <%%= @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%>">Click me!</a>
11
+
12
+ <%%= @url %>
@@ -0,0 +1,74 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ .fieldWithErrors {
20
+ padding: 2px;
21
+ background-color: red;
22
+ display: table;
23
+ }
24
+
25
+ #errorExplanation {
26
+ width: 400px;
27
+ border: 2px solid red;
28
+ padding: 7px;
29
+ padding-bottom: 12px;
30
+ margin-bottom: 20px;
31
+ background-color: #f0f0f0;
32
+ }
33
+
34
+ #errorExplanation h2 {
35
+ text-align: left;
36
+ font-weight: bold;
37
+ padding: 5px 5px 5px 15px;
38
+ font-size: 12px;
39
+ margin: -7px;
40
+ background-color: #c00;
41
+ color: #fff;
42
+ }
43
+
44
+ #errorExplanation p {
45
+ color: #333;
46
+ margin-bottom: 0;
47
+ padding: 5px;
48
+ }
49
+
50
+ #errorExplanation ul li {
51
+ font-size: 12px;
52
+ list-style: square;
53
+ }
54
+
55
+ div.uploadStatus {
56
+ margin: 5px;
57
+ }
58
+
59
+ div.progressBar {
60
+ margin: 5px;
61
+ }
62
+
63
+ div.progressBar div.border {
64
+ background-color: #fff;
65
+ border: 1px solid grey;
66
+ width: 100%;
67
+ }
68
+
69
+ div.progressBar div.background {
70
+ background-color: #333;
71
+ height: 18px;
72
+ width: 0%;
73
+ }
74
+
data/templates/user.rb ADDED
@@ -0,0 +1,101 @@
1
+ require 'digest/sha1'
2
+
3
+ # this model expects a certain database layout and its based on the name/login pattern.
4
+ class <%= class_name %> < ActiveRecord::Base
5
+ attr_accessor :password_needs_confirmation
6
+
7
+ after_save '@password_needs_confirmation = false'
8
+ after_validation :crypt_password
9
+
10
+ validates_presence_of :login, :on => :create
11
+ validates_length_of :login, :within => 3..40, :on => :create
12
+ validates_uniqueness_of :login, :on => :create
13
+ validates_uniqueness_of :email, :on => :create
14
+
15
+ validates_presence_of :password, :if => :validate_password?
16
+ validates_confirmation_of :password, :if => :validate_password?
17
+ validates_length_of :password, { :minimum => 5, :if => :validate_password? }
18
+ validates_length_of :password, { :maximum => 40, :if => :validate_password? }
19
+
20
+ def initialize(attributes = nil)
21
+ super
22
+ @password_needs_confirmation = false
23
+ end
24
+
25
+ def self.authenticate(login, pass)
26
+ u = find_first(["login = ? AND verified = TRUE AND deleted = FALSE", login])
27
+ return nil if u.nil?
28
+ find_first(["login = ? AND salted_password = ? AND verified = TRUE", login, salted_password(u.salt, hashed(pass))])
29
+ end
30
+
31
+ def self.authenticate_by_token(id, token)
32
+ # Allow logins for deleted accounts, but only via this method (and
33
+ # not the regular authenticate call)
34
+ logger.info "Attempting authorization of #{id} with #{token}"
35
+ u = find_first(["id = ? AND security_token = ?", id, token])
36
+ if u
37
+ logger.info "Authenticated by token: #{u.inspect}"
38
+ else
39
+ logger.info "Not authenticated" if u.nil?
40
+ end
41
+ return nil if (u.nil? or u.token_expired?)
42
+ u.update_attribute :verified, true
43
+ return u
44
+ end
45
+
46
+ def token_expired?
47
+ self.security_token and self.token_expiry and (Clock.now > self.token_expiry)
48
+ end
49
+
50
+ def generate_security_token
51
+ if self.security_token.nil? or self.token_expiry.nil? or (Clock.now.to_i + token_lifetime / 2) >= self.token_expiry.to_i
52
+ token = new_security_token
53
+ return token
54
+ else
55
+ return self.security_token
56
+ end
57
+ end
58
+
59
+ def change_password(pass, confirm = nil)
60
+ self.password = pass
61
+ self.password_confirmation = confirm.nil? ? pass : confirm
62
+ @password_needs_confirmation = true
63
+ end
64
+
65
+ def token_lifetime
66
+ <%= class_name %>System::CONFIG[:security_token_life_hours] * 60 * 60
67
+ end
68
+
69
+
70
+ protected
71
+
72
+ attr_accessor :password, :password_confirmation
73
+
74
+ def validate_password?
75
+ @password_needs_confirmation
76
+ end
77
+
78
+ def self.hashed(str)
79
+ return Digest::SHA1.hexdigest("change-me--#{str}--")[0..39]
80
+ end
81
+
82
+ def crypt_password
83
+ if @password_needs_confirmation
84
+ write_attribute("salt", self.class.hashed("salt-#{Clock.now}"))
85
+ write_attribute("salted_password", self.class.salted_password(salt, self.class.hashed(@password)))
86
+ end
87
+ end
88
+
89
+ def new_security_token
90
+ expiry = Time.at(Clock.now.to_i + token_lifetime)
91
+ write_attribute('security_token', self.class.hashed(self.salted_password + Clock.now.to_i.to_s + rand.to_s))
92
+ write_attribute('token_expiry', expiry)
93
+ update_without_callbacks
94
+ return self.security_token
95
+ end
96
+
97
+ def self.salted_password(salt, hashed_password)
98
+ hashed(salt + hashed_password)
99
+ end
100
+ end
101
+
@@ -0,0 +1,18 @@
1
+ CREATE TABLE <%= plural_name %> (
2
+ id <%%= @pk %>,
3
+ login VARCHAR(80) NOT NULL,
4
+ salted_password VARCHAR(40) NOT NULL,
5
+ email VARCHAR(60) NOT NULL,
6
+ first_name VARCHAR(40) default NULL,
7
+ last_name VARCHAR(40) default NULL,
8
+ salt CHAR(40) NOT NULL,
9
+ verified INT default 0,
10
+ role VARCHAR(40) default NULL,
11
+ security_token CHAR(40) default NULL,
12
+ token_expiry <%%= @datetime %> default NULL,
13
+ created_at <%%= @datetime %> default NULL,
14
+ updated_at <%%= @datetime %> default NULL,
15
+ logged_in_at <%%= @datetime %> default NULL,
16
+ deleted INT default 0,
17
+ delete_after <%%= @datetime %> default NULL
18
+ ) <%%= @options %>;
@@ -0,0 +1,133 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class <%= class_name %>Test < Test::Unit::TestCase
4
+ fixtures :<%= plural_name %>
5
+ self.use_transactional_fixtures = false
6
+
7
+ def test_authenticate
8
+ assert_equal <%= plural_name %>(:tesla), <%= class_name %>.authenticate(<%= plural_name %>(:tesla).login, "atest")
9
+ assert_nil <%= class_name %>.authenticate("nontesla", "atest")
10
+ assert_nil <%= class_name %>.authenticate(<%= plural_name %>(:tesla), "wrong password")
11
+ end
12
+
13
+ def test_authenticate_by_token
14
+ <%= singular_name %> = <%= plural_name %>(:unverified_<%= singular_name %>)
15
+ assert_equal <%= singular_name %>, <%= class_name %>.authenticate_by_token(<%= singular_name %>.id, <%= singular_name %>.security_token)
16
+ end
17
+
18
+ def test_authenticate_by_token__fails_if_expired
19
+ <%= singular_name %> = <%= plural_name %>(:unverified_<%= singular_name %>)
20
+ Clock.time = Clock.now + 2.days
21
+ assert_nil <%= class_name %>.authenticate_by_token(<%= singular_name %>.id, <%= singular_name %>.security_token)
22
+ end
23
+
24
+ def test_authenticate_by_token__fails_if_bad_token
25
+ <%= singular_name %> = <%= plural_name %>(:unverified_<%= singular_name %>)
26
+ assert_nil <%= class_name %>.authenticate_by_token(<%= singular_name %>.id, 'bad_token')
27
+ end
28
+
29
+ def test_authenticate_by_token__fails_if_bad_id
30
+ <%= singular_name %> = <%= plural_name %>(:unverified_<%= singular_name %>)
31
+ assert_nil <%= class_name %>.authenticate_by_token(-1, <%= singular_name %>.security_token)
32
+ end
33
+
34
+ def test_change_password
35
+ <%= singular_name %> = <%= plural_name %>(:long_<%= singular_name %>)
36
+ <%= singular_name %>.change_password("a new password")
37
+ <%= singular_name %>.save
38
+ assert_equal <%= singular_name %>, <%= class_name %>.authenticate(<%= singular_name %>.login, "a new password")
39
+ assert_nil <%= class_name %>.authenticate(<%= singular_name %>.login, "alongtest")
40
+ end
41
+
42
+ def test_generate_security_token
43
+ <%= singular_name %> = <%= class_name %>.new :login => '<%= singular_name %>', :email => '<%= singular_name %>@example.com', :salt => 'salt', :salted_password => 'tlas'
44
+ <%= singular_name %>.save
45
+ token = <%= singular_name %>.generate_security_token
46
+ assert_not_nil token
47
+ <%= singular_name %>.reload
48
+ assert_equal token, <%= singular_name %>.security_token
49
+ assert_equal (Clock.now + <%= singular_name %>.token_lifetime).to_i, <%= singular_name %>.token_expiry.to_i
50
+ end
51
+
52
+ def test_generate_security_token__reuses_token_when_not_stale
53
+ <%= singular_name %> = <%= plural_name %>(:unverified_<%= singular_name %>)
54
+ Clock.time = Clock.now + <%= singular_name %>.token_lifetime/2 - 1
55
+ assert_equal <%= singular_name %>.security_token, <%= singular_name %>.generate_security_token
56
+ end
57
+
58
+ def test_generate_security_token__generates_new_token_when_getting_stale
59
+ <%= singular_name %> = <%= plural_name %>(:unverified_<%= singular_name %>)
60
+ Clock.time = Clock.now + <%= singular_name %>.token_lifetime/2
61
+ assert_not_equal <%= singular_name %>.security_token, <%= singular_name %>.generate_security_token
62
+ end
63
+
64
+ def test_change_password__disallowed_passwords
65
+ u = <%= class_name %>.new
66
+ u.login = "test_<%= singular_name %>"
67
+ u.email = 'disallowed_password@example.com'
68
+
69
+ u.change_password("tiny")
70
+ assert !u.save
71
+ assert u.errors.invalid?('password')
72
+
73
+ u.change_password("hugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehuge")
74
+ assert !u.save
75
+ assert u.errors.invalid?('password')
76
+
77
+ u.change_password("")
78
+ assert !u.save
79
+ assert u.errors.invalid?('password')
80
+
81
+ u.change_password("a_s3cure_p4ssword")
82
+ assert u.save
83
+ assert u.errors.empty?
84
+ end
85
+
86
+ def test_validates_login
87
+ u = <%= class_name %>.new
88
+ u.change_password("teslas_secure_password")
89
+ u.email = 'bad_login_tesla@example.com'
90
+
91
+ u.login = "x"
92
+ assert !u.save
93
+ assert u.errors.invalid?(:login)
94
+
95
+ u.login = "hugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahug"
96
+ assert !u.save
97
+ assert u.errors.invalid?(:login)
98
+
99
+ u.login = ""
100
+ assert !u.save
101
+ assert u.errors.invalid?(:login)
102
+
103
+ u.login = "oktesla"
104
+ assert u.save
105
+ assert u.errors.empty?
106
+
107
+ end
108
+
109
+ def test_create
110
+ u = <%= class_name %>.new
111
+ u.login = "nonexisting_<%= singular_name %>"
112
+ u.email = 'nonexisting_email@example.com'
113
+ u.change_password("password")
114
+ assert u.save
115
+ end
116
+
117
+ def test_create__validates_unique_login
118
+ u = <%= class_name %>.new
119
+ u.login = <%= plural_name %>(:tesla).login
120
+ u.email = 'new@example.com'
121
+ u.change_password("password")
122
+ assert !u.save
123
+ end
124
+
125
+ def test_create__validates_unique_email
126
+ u = <%= class_name %>.new
127
+ u.login = 'new_<%= singular_name %>'
128
+ u.email= <%= plural_name %>(:tesla).email
129
+ u.change_password("password")
130
+ assert !u.save
131
+ end
132
+
133
+ end