goma 0.0.1.gamma → 0.0.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +13 -9
- data/Rakefile +14 -0
- data/goma.gemspec +6 -0
- data/lib/generators/goma/erb/templates/confirmation/new.html.erb +2 -2
- data/lib/generators/goma/erb/templates/password/edit.html.erb +6 -6
- data/lib/generators/goma/erb/templates/unlock/new.html.erb +1 -1
- data/lib/generators/goma/erb/templates/user/_form.html.erb +1 -1
- data/lib/generators/goma/erb/templates/user/new.html.erb +4 -0
- data/lib/generators/goma/mailer/erb/templates/activation_needed_email.text.erb +1 -1
- data/lib/generators/goma/model/active_record_generator.rb +1 -0
- data/lib/generators/goma/model/oauth/active_record_generator.rb +3 -3
- data/lib/generators/goma/resource_route/resource_route_generator.rb +13 -9
- data/lib/generators/goma/scaffold_controller/templates/confirmation_controller.rb +8 -4
- data/lib/generators/goma/scaffold_controller/templates/oauth_controller.rb +5 -0
- data/lib/generators/goma/scaffold_controller/templates/password_controller.rb +9 -4
- data/lib/generators/goma/scaffold_controller/templates/unlock_controller.rb +1 -0
- data/lib/generators/goma/scaffold_controller/templates/user_controller.rb +41 -1
- data/lib/goma/config.rb +4 -4
- data/lib/goma/models/authenticatable.rb +20 -9
- data/lib/goma/models/confirmable.rb +15 -9
- data/lib/goma/models/password_authenticatable.rb +2 -1
- data/lib/goma/models/recoverable.rb +1 -1
- data/lib/goma/models/validatable.rb +4 -4
- data/lib/goma/railtie.rb +1 -0
- data/lib/goma/version.rb +1 -1
- data/test/integration/confirmable_integration_test.rb +185 -0
- data/test/integration/lockable_integration_test.rb +49 -0
- data/test/integration/omniauthable_integration_test.rb +64 -12
- data/test/integration/password_authenticatable_integration_test.rb +40 -0
- data/test/integration/recoverable_integration_test.rb +96 -0
- data/test/integration/rememberable_integration_test.rb +14 -0
- data/test/models/confirmable_test.rb +6 -6
- data/test/models/omniauthable_test.rb +2 -2
- data/test/rails_app/app/controllers/authentications_controller.rb +5 -0
- data/test/rails_app/app/controllers/confirmations_controller.rb +8 -4
- data/test/rails_app/app/controllers/passwords_controller.rb +9 -4
- data/test/rails_app/app/controllers/unlocks_controller.rb +1 -0
- data/test/rails_app/app/controllers/users_controller.rb +25 -2
- data/test/rails_app/app/views/confirmations/new.html.erb +2 -2
- data/test/rails_app/app/views/layouts/application.html.erb +3 -1
- data/test/rails_app/app/views/passwords/edit.html.erb +6 -6
- data/test/rails_app/app/views/unlocks/new.html.erb +1 -1
- data/test/rails_app/app/views/user_mailer/activation_needed_email.text.erb +1 -1
- data/test/rails_app/app/views/users/_form.html.erb +1 -1
- data/test/rails_app/app/views/users/new.html.erb +1 -0
- data/test/rails_app/config/initializers/omniauth.rb +0 -1
- data/test/rails_app/config/routes.rb +6 -10
- data/test/rails_app/db/migrate/{20140512081308_create_users.rb → 20140515111009_create_users.rb} +0 -0
- data/test/rails_app/db/migrate/{20140512081309_create_authentications.rb → 20140515111010_create_authentications.rb} +0 -0
- data/test/rails_app/db/schema.rb +1 -1
- data/test/test_helper.rb +43 -0
- metadata +33 -13
- data/test/controllers/confirmations_controller_test.rb +0 -14
- data/test/controllers/users_controller_test.rb +0 -12
- data/test/integration/authenticatable_integration_test.rb +0 -26
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PasswordAuthenticatableIntegrationTest < ActionDispatch::IntegrationTest
|
4
|
+
def setup
|
5
|
+
@user = Fabricate(:user)
|
6
|
+
end
|
7
|
+
|
8
|
+
test 'should login' do
|
9
|
+
visit new_session_url
|
10
|
+
fill_in :username_or_email, with: @user.email
|
11
|
+
fill_in :password, with: 'password'
|
12
|
+
click_button 'Login'
|
13
|
+
assert_equal root_url, current_url
|
14
|
+
assert_equal @user, _current_user
|
15
|
+
end
|
16
|
+
|
17
|
+
test 'should redirect back' do
|
18
|
+
visit secret_url
|
19
|
+
assert_equal root_url, current_url
|
20
|
+
|
21
|
+
visit new_session_url
|
22
|
+
fill_in :username_or_email, with: @user.email
|
23
|
+
fill_in :password, with: 'password'
|
24
|
+
click_button 'Login'
|
25
|
+
assert_equal secret_url, current_url
|
26
|
+
assert_equal @user, _current_user
|
27
|
+
end
|
28
|
+
|
29
|
+
test 'should redirect back to url with parameters' do
|
30
|
+
visit secret_url(page: 100)
|
31
|
+
assert_equal root_url, current_url
|
32
|
+
|
33
|
+
visit new_session_url
|
34
|
+
fill_in :username_or_email, with: @user.email
|
35
|
+
fill_in :password, with: 'password'
|
36
|
+
click_button 'Login'
|
37
|
+
assert_equal secret_url(page: 100), current_url
|
38
|
+
assert_equal @user, _current_user
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RecoverableIntegrationTest < ActionDispatch::IntegrationTest
|
4
|
+
def setup
|
5
|
+
@user = Fabricate(:user)
|
6
|
+
end
|
7
|
+
|
8
|
+
test 'should work reset password proccess' do
|
9
|
+
Goma.token_generator.stubs(:friendly_token).returns('sesame')
|
10
|
+
visit new_password_url
|
11
|
+
fill_in :username_or_email, with: @user.email
|
12
|
+
assert_difference 'ActionMailer::Base.deliveries.size', 1 do
|
13
|
+
click_button 'Send me reset password instructions'
|
14
|
+
end
|
15
|
+
|
16
|
+
email = ActionMailer::Base.deliveries.last
|
17
|
+
assert_match %r{/passwords/sesame/edit}, email.body.encoded
|
18
|
+
|
19
|
+
visit edit_password_url('sesame')
|
20
|
+
fill_in :user_password, with: 'newpassword'
|
21
|
+
fill_in :user_password_confirmation, with: 'newpassword'
|
22
|
+
click_button 'Change my password'
|
23
|
+
assert_equal root_url, current_url
|
24
|
+
assert_equal @user, _current_user
|
25
|
+
|
26
|
+
@user.reload
|
27
|
+
assert @user.valid_password?('newpassword')
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'should execute validation check' do
|
31
|
+
Goma.token_generator.stubs(:friendly_token).returns('sesame')
|
32
|
+
visit new_password_url
|
33
|
+
fill_in :username_or_email, with: @user.email
|
34
|
+
assert_difference 'ActionMailer::Base.deliveries.size', 1 do
|
35
|
+
click_button 'Send me reset password instructions'
|
36
|
+
end
|
37
|
+
|
38
|
+
mail = ActionMailer::Base.deliveries.last
|
39
|
+
assert_match %r{/passwords/sesame/edit}, mail.body.encoded
|
40
|
+
|
41
|
+
visit edit_password_url('sesame')
|
42
|
+
fill_in :user_password, with: 'short'
|
43
|
+
fill_in :user_password_confirmation, with: 'short'
|
44
|
+
click_button 'Change my password'
|
45
|
+
assert_nil _current_user
|
46
|
+
@user.reload
|
47
|
+
assert @user.valid_password?('password')
|
48
|
+
|
49
|
+
fill_in :user_password, with: 'newpassword'
|
50
|
+
fill_in :user_password_confirmation, with: 'newpassword'
|
51
|
+
click_button 'Change my password'
|
52
|
+
assert_equal root_url, current_url
|
53
|
+
assert_equal @user, _current_user
|
54
|
+
|
55
|
+
@user.reload
|
56
|
+
assert @user.valid_password?('newpassword')
|
57
|
+
end
|
58
|
+
|
59
|
+
test 'should not accept wrong reset password token URL' do
|
60
|
+
Goma.token_generator.stubs(:friendly_token).returns('sesame')
|
61
|
+
visit new_password_url
|
62
|
+
fill_in :username_or_email, with: @user.email
|
63
|
+
click_button 'Send me reset password instructions'
|
64
|
+
|
65
|
+
visit edit_password_url('beans')
|
66
|
+
fill_in :user_password, with: 'newpassword'
|
67
|
+
fill_in :user_password_confirmation, with: 'newpassword'
|
68
|
+
click_button 'Change my password'
|
69
|
+
|
70
|
+
assert_match /You can't change your password in this page without coming from a password reset email/, _flash[:alert]
|
71
|
+
assert_match /Change my password/, page.body, 'should render "passwords/{token}/edit" template'
|
72
|
+
|
73
|
+
@user.reload
|
74
|
+
assert @user.valid_password?('password')
|
75
|
+
end
|
76
|
+
|
77
|
+
test 'should not accept expired reset password token URL' do
|
78
|
+
Goma.token_generator.stubs(:friendly_token).returns('sesame')
|
79
|
+
visit new_password_url
|
80
|
+
fill_in :username_or_email, with: @user.email
|
81
|
+
click_button 'Send me reset password instructions'
|
82
|
+
|
83
|
+
Timecop.travel 7.hours.from_now
|
84
|
+
visit edit_password_url('sesame')
|
85
|
+
fill_in :user_password, with: 'newpassword'
|
86
|
+
fill_in :user_password_confirmation, with: 'newpassword'
|
87
|
+
click_button 'Change my password'
|
88
|
+
|
89
|
+
assert_match /The password reset URL you visited has expired, please request a new one/, _flash[:alert]
|
90
|
+
assert_match /Forget your password\?/, page.body, 'should render "passwords/new" template'
|
91
|
+
|
92
|
+
@user.reload
|
93
|
+
assert @user.valid_password?('password')
|
94
|
+
Timecop.return
|
95
|
+
end
|
96
|
+
end
|
@@ -92,4 +92,18 @@ class RememberableIntegrationTest < ActionDispatch::IntegrationTest
|
|
92
92
|
refute request.env['warden'].user(:user)
|
93
93
|
end
|
94
94
|
end
|
95
|
+
|
96
|
+
test 'should exist remember view element' do
|
97
|
+
visit new_session_url
|
98
|
+
fill_in :username_or_email, with: @user.email
|
99
|
+
fill_in :password, with: 'password'
|
100
|
+
check :remember_me
|
101
|
+
click_button 'Login'
|
102
|
+
|
103
|
+
@user.reload
|
104
|
+
assert_equal root_url, current_url
|
105
|
+
assert_equal @user, _current_user
|
106
|
+
assert _cookies['remember_user_token']
|
107
|
+
assert_equal @user.serialize_into_cookie, _signed_cookie('remember_user_token')
|
108
|
+
end
|
95
109
|
end
|
@@ -27,7 +27,7 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
27
27
|
should "load user record with correct activation token" do
|
28
28
|
raw_token = @user.raw_confirmation_token
|
29
29
|
loaded_user = User.load_from_activation_token!(raw_token)
|
30
|
-
assert_equal @user
|
30
|
+
assert_equal @user, loaded_user
|
31
31
|
end
|
32
32
|
|
33
33
|
should "raise exception with incorrect activation token" do
|
@@ -54,7 +54,7 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
54
54
|
should "return user record and nil with correct activation token" do
|
55
55
|
raw_token = @user.raw_confirmation_token
|
56
56
|
loaded_user, error = User.load_from_activation_token_with_error(raw_token)
|
57
|
-
assert_equal @user
|
57
|
+
assert_equal @user, loaded_user
|
58
58
|
assert_nil error
|
59
59
|
end
|
60
60
|
|
@@ -82,7 +82,7 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
82
82
|
should "return user record with correct activation token" do
|
83
83
|
raw_token = @user.raw_confirmation_token
|
84
84
|
loaded_user = User.load_from_activation_token(raw_token)
|
85
|
-
assert_equal @user
|
85
|
+
assert_equal @user, loaded_user
|
86
86
|
end
|
87
87
|
|
88
88
|
should "return nil with incorrect activation token" do
|
@@ -148,7 +148,7 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
148
148
|
should "find user record with correct email confirmation token" do
|
149
149
|
raw_token = @user.raw_confirmation_token
|
150
150
|
loaded_user = User.load_from_email_confirmation_token!(raw_token)
|
151
|
-
assert_equal @user
|
151
|
+
assert_equal @user, loaded_user
|
152
152
|
end
|
153
153
|
|
154
154
|
should "not find user record with incorrect confirmation token" do
|
@@ -177,7 +177,7 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
177
177
|
should "return user record and nil with correct email_confirmation token" do
|
178
178
|
raw_token = @user.raw_confirmation_token
|
179
179
|
loaded_user, error = User.load_from_email_confirmation_token_with_error(raw_token)
|
180
|
-
assert_equal @user
|
180
|
+
assert_equal @user, loaded_user
|
181
181
|
assert_nil error
|
182
182
|
end
|
183
183
|
|
@@ -207,7 +207,7 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
207
207
|
should "return user record with correct email_confirmation token" do
|
208
208
|
raw_token = @user.raw_confirmation_token
|
209
209
|
loaded_user = User.load_from_email_confirmation_token(raw_token)
|
210
|
-
assert_equal @user
|
210
|
+
assert_equal @user, loaded_user
|
211
211
|
end
|
212
212
|
|
213
213
|
should "return nil with incorrect email_confirmation token" do
|
@@ -7,8 +7,8 @@ class OmniauthableTest < ActiveSupport::TestCase
|
|
7
7
|
end
|
8
8
|
|
9
9
|
should "create user from omniauth" do
|
10
|
-
User.any_instance.
|
11
|
-
Authentication.any_instance.
|
10
|
+
User.any_instance.expects(:fill_with_omniauth).with(@omniauth)
|
11
|
+
Authentication.any_instance.expects(:fill_with_omniauth).with(@omniauth)
|
12
12
|
|
13
13
|
u = nil
|
14
14
|
assert_difference ['User.count', 'Authentication.count'], 1 do
|
@@ -12,4 +12,9 @@ class AuthenticationsController < ApplicationController
|
|
12
12
|
force_login(user)
|
13
13
|
redirect_back_or_to root_url, notice: "Successfully authenticated from #{omniauth[:provider]} account."
|
14
14
|
end
|
15
|
+
|
16
|
+
def failure
|
17
|
+
flash[:alert] = "Could not authenticate you from #{params[:strategy].capitalize} because \"#{params[:message].humanize}\"."
|
18
|
+
redirect_to new_session_url
|
19
|
+
end
|
15
20
|
end
|
@@ -8,8 +8,7 @@ class ConfirmationsController < ApplicationController
|
|
8
8
|
# POST /confirmations
|
9
9
|
def create
|
10
10
|
@user = User.find_by_identifier(params[:username_or_email])
|
11
|
-
@user.
|
12
|
-
@user.send_activation_needed_email
|
11
|
+
@user.resend_activation_needed_email
|
13
12
|
|
14
13
|
redirect_to new_session_url, notice: "We are processing your request. You will receive new activation email in a few minutes."
|
15
14
|
end
|
@@ -39,14 +38,19 @@ class ConfirmationsController < ApplicationController
|
|
39
38
|
|
40
39
|
if @user
|
41
40
|
@user.confirm_email!
|
42
|
-
redirect_to
|
41
|
+
redirect_to root_url, notice: 'Your new email was successfully confirmed.'
|
43
42
|
else
|
44
43
|
if err == :token_expired
|
45
44
|
flash.now[:alert] = "Your email confirmation URL has expired, please change your email again."
|
46
45
|
else
|
47
46
|
flash.now[:alert] = "Email confirmation failed. Please make sure you used the full URL provided."
|
48
47
|
end
|
49
|
-
|
48
|
+
|
49
|
+
if current_user
|
50
|
+
render edit_user_url(current_user)
|
51
|
+
else
|
52
|
+
render root_url
|
53
|
+
end
|
50
54
|
end
|
51
55
|
end
|
52
56
|
end
|
@@ -16,16 +16,17 @@ class PasswordsController < ApplicationController
|
|
16
16
|
|
17
17
|
# GET /passwords/1/edit
|
18
18
|
def edit
|
19
|
-
@
|
19
|
+
@user = User.new
|
20
|
+
@user.raw_reset_password_token = params[:id]
|
20
21
|
end
|
21
22
|
|
22
23
|
# PATCH/PUT /passwords/1
|
23
24
|
def update
|
24
|
-
@user, err = User.load_from_reset_password_token_with_error(params[:
|
25
|
+
@user, err = User.load_from_reset_password_token_with_error(params[:user][:raw_reset_password_token])
|
25
26
|
|
26
27
|
if @user
|
27
28
|
@user.unlock_access! if @user.lockable? && @user.access_locked?
|
28
|
-
@user.change_password!(params[:password], params[:password_confirmation])
|
29
|
+
@user.change_password!(params[:user][:password], params[:user][:password_confirmation])
|
29
30
|
force_login(@user)
|
30
31
|
redirect_back_or_to root_url, notice: 'Your password was changed successfully. You are now logged in.'
|
31
32
|
else
|
@@ -33,9 +34,13 @@ class PasswordsController < ApplicationController
|
|
33
34
|
flash.now[:alert] = "The password reset URL you visited has expired, please request a new one."
|
34
35
|
render :new
|
35
36
|
else
|
36
|
-
|
37
|
+
@user = User.new
|
38
|
+
flash.now[:alert] = "You can't change your password in this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
|
37
39
|
render :edit
|
38
40
|
end
|
39
41
|
end
|
42
|
+
rescue ActiveRecord::RecordInvalid
|
43
|
+
@user.raw_reset_password_token = params[:user][:raw_reset_password_token]
|
44
|
+
render :edit
|
40
45
|
end
|
41
46
|
end
|
@@ -4,6 +4,7 @@ class UnlocksController < ApplicationController
|
|
4
4
|
def show
|
5
5
|
@user, err = User.load_from_unlock_token_with_error(params[:id])
|
6
6
|
if @user
|
7
|
+
@user.unlock_access!
|
7
8
|
flash[:notice] = "Your account has been unlocked successfully. Please continue to login."
|
8
9
|
redirect_to new_session_url
|
9
10
|
else
|
@@ -1,4 +1,5 @@
|
|
1
1
|
class UsersController < ApplicationController
|
2
|
+
before_action :require_user_login, only: [:edit, :update, :destroy]
|
2
3
|
before_action :set_user, only: [:show, :edit, :update, :destroy]
|
3
4
|
|
4
5
|
# GET /users
|
@@ -17,6 +18,7 @@ class UsersController < ApplicationController
|
|
17
18
|
|
18
19
|
# GET /users/1/edit
|
19
20
|
def edit
|
21
|
+
not_authenticated unless current_user = @user
|
20
22
|
end
|
21
23
|
|
22
24
|
# POST /users
|
@@ -26,14 +28,18 @@ class UsersController < ApplicationController
|
|
26
28
|
if @user.save
|
27
29
|
redirect_to new_session_url, notice: "You have signed up successfully. However, we could not sign you in because your account is not yet activated. You will receive an email with instructions about how to activate your account in a few minutes."
|
28
30
|
else
|
29
|
-
render :new
|
31
|
+
update_email or render :new
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
33
35
|
# PATCH/PUT /users/1
|
34
36
|
def update
|
37
|
+
not_authenticated unless current_user = @user
|
35
38
|
if @user.update(user_params)
|
36
|
-
|
39
|
+
flash[:notice] = @user.raw_confirmation_token ?
|
40
|
+
'You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirmation link to finalize confirming your new email address.' :
|
41
|
+
'You updated your account successfully'
|
42
|
+
redirect_to @user
|
37
43
|
else
|
38
44
|
render :edit
|
39
45
|
end
|
@@ -41,6 +47,7 @@ class UsersController < ApplicationController
|
|
41
47
|
|
42
48
|
# DELETE /users/1
|
43
49
|
def destroy
|
50
|
+
not_authenticated unless current_user = @user
|
44
51
|
@user.destroy
|
45
52
|
redirect_to users_url, notice: 'User was successfully destroyed.'
|
46
53
|
end
|
@@ -55,4 +62,20 @@ class UsersController < ApplicationController
|
|
55
62
|
def user_params
|
56
63
|
params.require(:user).permit(:username, :email, :password, :password_confirmation)
|
57
64
|
end
|
65
|
+
|
66
|
+
def update_email
|
67
|
+
@user = User.find_by(username: params[:user][:username])
|
68
|
+
if @user.activated?
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
if ( Time.now <= @user.confirmation_token_sent_at + 259200 ) &&
|
73
|
+
!@user.valid_password?(params[:user][:password])
|
74
|
+
flash[:alert] = 'This username was already registered. If you want to change your email address, please make sure you entered correct password.'
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
@user.resend_activation_needed_email(to: params[:user][:email])
|
79
|
+
redirect_to new_session_url
|
80
|
+
end
|
58
81
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
<h1>Resend activation instructions</h1>
|
2
2
|
|
3
|
-
<%= form_tag(
|
3
|
+
<%= form_tag(confirmations_url, method: :post) do %>
|
4
4
|
<div class="field">
|
5
5
|
<%= label_tag :username_or_email %><br>
|
6
|
-
<%= text_field_tag :username_or_email
|
6
|
+
<%= text_field_tag :username_or_email %>
|
7
7
|
</div>
|
8
8
|
<div class="actions">
|
9
9
|
<%= submit_tag "Resend activation instructions" %>
|
@@ -1,18 +1,18 @@
|
|
1
1
|
<h1>Change your password</h1>
|
2
2
|
|
3
|
-
<%= form_for(@
|
4
|
-
<% if @
|
3
|
+
<%= form_for(@user, url: password_url, html: {method: :put}) do |f| %>
|
4
|
+
<% if @user && @user.errors.any? %>
|
5
5
|
<div id="error_explanation">
|
6
|
-
<h2><%= pluralize(@
|
6
|
+
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
|
7
7
|
|
8
8
|
<ul>
|
9
|
-
<% @
|
9
|
+
<% @user.errors.full_messages.each do |msg| %>
|
10
10
|
<li><%= msg %></li>
|
11
11
|
<% end %>
|
12
12
|
</ul>
|
13
13
|
</div>
|
14
14
|
<% end %>
|
15
|
-
|
15
|
+
<%= f.hidden_field :raw_reset_password_token %>
|
16
16
|
<div class="field">
|
17
17
|
<%= f.label :password %><br>
|
18
18
|
<%= f.password_field :password %>
|
@@ -22,6 +22,6 @@
|
|
22
22
|
<%= f.password_field :password_confirmation %>
|
23
23
|
</div>
|
24
24
|
<div class="actions">
|
25
|
-
<%= f.submit %>
|
25
|
+
<%= f.submit 'Change my password' %>
|
26
26
|
</div>
|
27
27
|
<% end %>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<%= form_tag(unlocks_url, method: :post) do %>
|
4
4
|
<div class="field">
|
5
5
|
<%= label_tag :username_or_email %><br>
|
6
|
-
<%= text_field_tag :username_or_email
|
6
|
+
<%= text_field_tag :username_or_email %>
|
7
7
|
</div>
|
8
8
|
<div class="actions">
|
9
9
|
<%= submit_tag "Resend unlock instructions" %>
|