authpwn_rails 0.10.5 → 0.10.6
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -8
- data/Gemfile.lock +1 -1
- data/VERSION +1 -1
- data/app/models/credentials/email.rb +12 -4
- data/app/models/credentials/token.rb +106 -0
- data/app/models/tokens/email_verification.rb +42 -0
- data/app/models/tokens/one_time.rb +16 -0
- data/app/models/tokens/password_reset.rb +27 -0
- data/authpwn_rails.gemspec +36 -11
- data/lib/authpwn_rails.rb +2 -0
- data/lib/authpwn_rails/generators/all_generator.rb +20 -2
- data/lib/authpwn_rails/generators/templates/credentials.yml +21 -0
- data/lib/authpwn_rails/generators/templates/session/new.html.erb +10 -5
- data/lib/authpwn_rails/generators/templates/session/password_change.html.erb +37 -0
- data/lib/authpwn_rails/generators/templates/session_controller.rb +20 -2
- data/lib/authpwn_rails/generators/templates/session_controller_test.rb +71 -0
- data/lib/authpwn_rails/generators/templates/session_mailer.rb +26 -0
- data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.html.erb +23 -0
- data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.text.erb +11 -0
- data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.html.erb +23 -0
- data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.text.erb +11 -0
- data/lib/authpwn_rails/generators/templates/session_mailer_test.rb +37 -0
- data/lib/authpwn_rails/routes.rb +50 -0
- data/lib/authpwn_rails/session_controller.rb +129 -0
- data/lib/authpwn_rails/session_mailer.rb +66 -0
- data/test/{email_credential_test.rb → credentials/email_credential_test.rb} +1 -1
- data/test/credentials/email_verification_token_test.rb +78 -0
- data/test/{facebook_credential_test.rb → credentials/facebook_credential_test.rb} +1 -1
- data/test/credentials/one_time_token_credential_test.rb +84 -0
- data/test/{password_credential_test.rb → credentials/password_credential_test.rb} +1 -1
- data/test/credentials/password_reset_token_test.rb +72 -0
- data/test/credentials/token_crendential_test.rb +102 -0
- data/test/fixtures/bare_session/forbidden.html.erb +20 -0
- data/test/fixtures/bare_session/home.html.erb +5 -0
- data/test/fixtures/bare_session/new.html.erb +32 -0
- data/test/fixtures/bare_session/password_change.html.erb +30 -0
- data/test/fixtures/bare_session/welcome.html.erb +5 -0
- data/test/helpers/action_mailer.rb +8 -0
- data/test/helpers/routes.rb +8 -2
- data/test/routes_test.rb +31 -0
- data/test/session_controller_api_test.rb +310 -15
- data/test/session_mailer_api_test.rb +67 -0
- data/test/test_helper.rb +3 -1
- data/test/{email_field_test.rb → user_extensions/email_field_test.rb} +1 -1
- data/test/{facebook_fields_test.rb → user_extensions/facebook_fields_test.rb} +1 -1
- data/test/{password_field_test.rb → user_extensions/password_field_test.rb} +1 -1
- metadata +49 -24
@@ -0,0 +1,66 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Authpwn
|
3
|
+
|
4
|
+
# Included by the session mailer class.
|
5
|
+
#
|
6
|
+
# Parts of the codebase assume the mailer will be named SessionMailer.
|
7
|
+
module SessionMailer
|
8
|
+
# Creates an e-mail containing a verification token for the e-mail address.
|
9
|
+
#
|
10
|
+
# Params:
|
11
|
+
# token:: the e-mail confirmation token
|
12
|
+
# host:: the server's hostname (e.g. "localhost:3000")
|
13
|
+
def email_verification_email(token, host)
|
14
|
+
@token, @host = token, host
|
15
|
+
|
16
|
+
hostname = host.split(':', 2).first # Strip out any port.
|
17
|
+
|
18
|
+
mail :to => @token.email,
|
19
|
+
:subject => email_verification_subject(token, hostname),
|
20
|
+
:from => email_verification_from(token, hostname)
|
21
|
+
end
|
22
|
+
|
23
|
+
# The subject line in an e-mail verification e-mail.
|
24
|
+
#
|
25
|
+
# The authpwn generator encourages applications to override this method.
|
26
|
+
def email_verification_subject(token, server_hostname)
|
27
|
+
"#{server_hostname} e-mail verification"
|
28
|
+
end
|
29
|
+
|
30
|
+
# The sender e-mail address for an e-mail verification e-mail.
|
31
|
+
#
|
32
|
+
# The authpwn generator encourages applications to override this method.
|
33
|
+
def email_verification_from(token, server_hostname)
|
34
|
+
%Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
|
35
|
+
end
|
36
|
+
|
37
|
+
# Creates an e-mail containing a password reset token.
|
38
|
+
#
|
39
|
+
# Params:
|
40
|
+
# email:: the email to send the token to
|
41
|
+
# token:: the password reset token
|
42
|
+
# host:: the server's hostname (e.g. "localhost:3000")
|
43
|
+
def reset_password_email(email, token, host)
|
44
|
+
@email, @token, @host = email, token, host
|
45
|
+
|
46
|
+
hostname = host.split(':', 2).first # Strip out any port.
|
47
|
+
mail :to => email, :subject => reset_password_subject(token, hostname),
|
48
|
+
:from => reset_password_from(token, hostname)
|
49
|
+
end
|
50
|
+
|
51
|
+
# The subject line in a password reset e-mail.
|
52
|
+
#
|
53
|
+
# The authpwn generator encourages applications to override this method.
|
54
|
+
def reset_password_subject(token, server_hostname)
|
55
|
+
"#{server_hostname} password reset"
|
56
|
+
end
|
57
|
+
|
58
|
+
# The sender e-mail address for a password reset e-mail.
|
59
|
+
#
|
60
|
+
# The authpwn generator encourages applications to override this method.
|
61
|
+
def reset_password_from(token, server_hostname)
|
62
|
+
%Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
|
63
|
+
end
|
64
|
+
end # namespace Authpwn::SessionMailer
|
65
|
+
|
66
|
+
end # namespace Authpwn
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class EmailVerificationTokenCredentialTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
@credential = Tokens::EmailVerification.new(
|
6
|
+
:code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo',
|
7
|
+
:key => 'jane@gmail.com')
|
8
|
+
@credential.user = users(:jane)
|
9
|
+
end
|
10
|
+
|
11
|
+
test 'setup' do
|
12
|
+
assert @credential.valid?
|
13
|
+
end
|
14
|
+
|
15
|
+
test 'code required' do
|
16
|
+
@credential.code = nil
|
17
|
+
assert !@credential.valid?
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'code uniqueness' do
|
21
|
+
@credential.code = credentials(:john_token).code
|
22
|
+
assert !@credential.valid?
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'email required' do
|
26
|
+
@credential.email = nil
|
27
|
+
assert !@credential.valid?
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'user required' do
|
31
|
+
@credential.user = nil
|
32
|
+
assert !@credential.valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
test 'email_credential' do
|
36
|
+
assert_equal credentials(:jane_email), @credential.email_credential
|
37
|
+
assert_equal credentials(:john_email),
|
38
|
+
credentials(:john_email_token).email_credential
|
39
|
+
|
40
|
+
@credential.email = 'bill@gmail.com'
|
41
|
+
assert_nil @credential.email_credential
|
42
|
+
end
|
43
|
+
|
44
|
+
test 'spend verifies the e-mail and destroys the token' do
|
45
|
+
email_credential = credentials(:john_email)
|
46
|
+
assert !email_credential.verified, 'bad setup'
|
47
|
+
credential = credentials(:john_email_token)
|
48
|
+
assert_equal Tokens::EmailVerification, credential.class, 'bad setup'
|
49
|
+
|
50
|
+
assert_difference 'Credential.count', -1 do
|
51
|
+
credential.spend
|
52
|
+
end
|
53
|
+
assert credential.frozen?, 'not destroyed'
|
54
|
+
assert email_credential.reload.verified?, 'e-mail not verified'
|
55
|
+
end
|
56
|
+
|
57
|
+
test 'spend does not verify a random e-mail and still destroys the token' do
|
58
|
+
email_credential = credentials(:john_email)
|
59
|
+
credential = credentials(:john_email_token)
|
60
|
+
credential.email = 'bill@gmail.com'
|
61
|
+
|
62
|
+
assert_difference 'Credential.count', -1 do
|
63
|
+
credential.spend
|
64
|
+
end
|
65
|
+
assert credential.frozen?, 'not destroyed'
|
66
|
+
assert !email_credential.reload.verified?, 'e-mail wrongly verified'
|
67
|
+
end
|
68
|
+
|
69
|
+
test 'random_for' do
|
70
|
+
token = Tokens::EmailVerification.random_for credentials(:jane_email)
|
71
|
+
assert token.valid?, 'valid token'
|
72
|
+
assert_equal users(:jane), token.user
|
73
|
+
assert_equal credentials(:jane_email).email, token.email
|
74
|
+
assert_equal Tokens::EmailVerification, token.class
|
75
|
+
assert !token.new_record?, 'saved token'
|
76
|
+
assert_operator users(:jane).credentials, :include?, token
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class OneTimeTokenCredentialTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
@credential = Tokens::OneTime.new(
|
6
|
+
:code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo')
|
7
|
+
@credential.user = users(:bill)
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'setup' do
|
11
|
+
assert @credential.valid?
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'code required' do
|
15
|
+
@credential.code = nil
|
16
|
+
assert !@credential.valid?
|
17
|
+
end
|
18
|
+
|
19
|
+
test 'code uniqueness' do
|
20
|
+
@credential.code = credentials(:john_token).code
|
21
|
+
assert !@credential.valid?
|
22
|
+
end
|
23
|
+
|
24
|
+
test 'user presence' do
|
25
|
+
@credential.user = nil
|
26
|
+
assert !@credential.valid?
|
27
|
+
end
|
28
|
+
|
29
|
+
test 'spend destroys the token' do
|
30
|
+
credential = credentials(:john_token)
|
31
|
+
assert_equal Tokens::OneTime, credential.class, 'bad setup'
|
32
|
+
|
33
|
+
assert_difference 'Credential.count', -1 do
|
34
|
+
credential.spend
|
35
|
+
end
|
36
|
+
assert credential.frozen?, 'not destroyed'
|
37
|
+
end
|
38
|
+
|
39
|
+
test 'authenticate spends the token' do
|
40
|
+
john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
|
41
|
+
bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
|
42
|
+
assert_difference 'Credential.count', -1, 'token spent' do
|
43
|
+
assert_equal users(:john), Credentials::Token.authenticate(john)
|
44
|
+
end
|
45
|
+
assert_no_difference 'Credential.count', 'token mistakenly spent' do
|
46
|
+
assert_equal :invalid, Credentials::Token.authenticate(bogus)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
test 'authenticate calls User#auth_bounce_reason' do
|
51
|
+
john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
|
52
|
+
jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
|
53
|
+
bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
|
54
|
+
|
55
|
+
with_blocked_credential credentials(:john_token), :reason do
|
56
|
+
assert_no_difference 'Credential.count', 'no token spent' do
|
57
|
+
assert_equal :reason, Credentials::Token.authenticate(john)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
test 'instance authenticate spends the token' do
|
63
|
+
assert_difference 'Credential.count', -1, 'token spent' do
|
64
|
+
assert_equal users(:john), credentials(:john_token).authenticate
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
test 'instance authenticate calls User#auth_bounce_reason' do
|
69
|
+
with_blocked_credential credentials(:john_token), :reason do
|
70
|
+
assert_no_difference 'Credential.count', 'token mistakenly spent' do
|
71
|
+
assert_equal :reason, credentials(:john_token).authenticate
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
test 'random_for' do
|
77
|
+
token = Tokens::OneTime.random_for users(:john)
|
78
|
+
assert token.valid?, 'valid token'
|
79
|
+
assert_equal users(:john), token.user
|
80
|
+
assert_equal Tokens::OneTime, token.class
|
81
|
+
assert !token.new_record?, 'saved token'
|
82
|
+
assert_operator users(:john).credentials, :include?, token
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class PasswordVerificationTokenTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
@credential = Tokens::PasswordReset.new(
|
6
|
+
:code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo')
|
7
|
+
@credential.user = users(:john)
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'setup' do
|
11
|
+
assert @credential.valid?
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'code required' do
|
15
|
+
@credential.code = nil
|
16
|
+
assert !@credential.valid?
|
17
|
+
end
|
18
|
+
|
19
|
+
test 'code uniqueness' do
|
20
|
+
@credential.code = credentials(:john_token).code
|
21
|
+
assert !@credential.valid?
|
22
|
+
end
|
23
|
+
|
24
|
+
test 'user required' do
|
25
|
+
@credential.user = nil
|
26
|
+
assert !@credential.valid?
|
27
|
+
end
|
28
|
+
|
29
|
+
test 'password_credential' do
|
30
|
+
assert_equal credentials(:john_password), @credential.password_credential
|
31
|
+
assert_equal credentials(:jane_password),
|
32
|
+
credentials(:jane_password_token).password_credential
|
33
|
+
|
34
|
+
@credential.user = users(:bill)
|
35
|
+
assert_nil @credential.password_credential
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'spend blanks out the password and destroys the token' do
|
39
|
+
password_credential = credentials(:jane_password)
|
40
|
+
credential = credentials(:jane_password_token)
|
41
|
+
assert_equal Tokens::PasswordReset, credential.class, 'bad setup'
|
42
|
+
|
43
|
+
assert_difference 'Credential.count', -2 do
|
44
|
+
assert_difference 'Credentials::Password.count', -1 do
|
45
|
+
credential.spend
|
46
|
+
end
|
47
|
+
end
|
48
|
+
assert credential.frozen?, 'not destroyed'
|
49
|
+
assert_nil Credential.where(:id => password_credential.id).first,
|
50
|
+
'password not blanked out'
|
51
|
+
end
|
52
|
+
|
53
|
+
test 'spend works on password-less user and still destroys the token' do
|
54
|
+
password_credential = credentials(:jane_password)
|
55
|
+
password_credential.destroy
|
56
|
+
credential = credentials(:jane_password_token)
|
57
|
+
|
58
|
+
assert_difference 'Credential.count', -1 do
|
59
|
+
credential.spend
|
60
|
+
end
|
61
|
+
assert credential.frozen?, 'not destroyed'
|
62
|
+
end
|
63
|
+
|
64
|
+
test 'random_for' do
|
65
|
+
token = Tokens::PasswordReset.random_for users(:john)
|
66
|
+
assert token.valid?, 'valid token'
|
67
|
+
assert_equal users(:john), token.user
|
68
|
+
assert_equal Tokens::PasswordReset, token.class
|
69
|
+
assert !token.new_record?, 'saved token'
|
70
|
+
assert_operator users(:john).credentials, :include?, token
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TokenCredentialTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
@credential = Credentials::Token.new(
|
6
|
+
:code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo')
|
7
|
+
@credential.user = users(:bill)
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'setup' do
|
11
|
+
assert @credential.valid?
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'code required' do
|
15
|
+
@credential.code = nil
|
16
|
+
assert !@credential.valid?
|
17
|
+
end
|
18
|
+
|
19
|
+
test 'code uniqueness' do
|
20
|
+
@credential.code = credentials(:john_token).code
|
21
|
+
assert !@credential.valid?
|
22
|
+
end
|
23
|
+
|
24
|
+
test 'user presence' do
|
25
|
+
@credential.user = nil
|
26
|
+
assert !@credential.valid?
|
27
|
+
end
|
28
|
+
|
29
|
+
test 'spend does nothing' do
|
30
|
+
credential = credentials(:jane_token)
|
31
|
+
assert_equal Credentials::Token, credential.class, 'bad setup'
|
32
|
+
|
33
|
+
assert_no_difference 'Credential.count' do
|
34
|
+
credential.spend
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'random_for' do
|
39
|
+
token = Credentials::Token.random_for users(:john)
|
40
|
+
assert token.valid?, 'valid token'
|
41
|
+
assert_equal users(:john), token.user
|
42
|
+
assert_equal Credentials::Token, token.class
|
43
|
+
assert !token.new_record?, 'saved token'
|
44
|
+
assert_operator users(:john).credentials, :include?, token
|
45
|
+
end
|
46
|
+
|
47
|
+
test 'with_code' do
|
48
|
+
john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
|
49
|
+
john2 = 'bDSU4tzfjuob79e3R0ykLcOGTBBYvuBWWJ9V06tQrCE'
|
50
|
+
jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
|
51
|
+
bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
|
52
|
+
assert_equal credentials(:john_token), Credentials::Token.with_code(john)
|
53
|
+
assert_equal credentials(:jane_token), Credentials::Token.with_code(jane)
|
54
|
+
assert_equal credentials(:john_email_token),
|
55
|
+
Credentials::Token.with_code(john2)
|
56
|
+
assert_nil Credentials::Token.with_code(bogus)
|
57
|
+
assert_nil Credentials::Token.with_code('john@gmail.com')
|
58
|
+
assert_nil Credentials::Token.with_code(credentials(:jane_email).name)
|
59
|
+
end
|
60
|
+
|
61
|
+
test 'find_by_param' do
|
62
|
+
assert_equal credentials(:john_token), Credentials::Token.
|
63
|
+
find_by_param(credentials(:john_token).to_param)
|
64
|
+
assert_equal credentials(:jane_token), Credentials::Token.
|
65
|
+
find_by_param(credentials(:jane_token).to_param)
|
66
|
+
assert_equal nil, Credentials::Token.find_by_param('bogus token')
|
67
|
+
assert_equal nil, Credentials::Token.find_by_param(nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
test 'class authenticate' do
|
71
|
+
john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
|
72
|
+
jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
|
73
|
+
bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
|
74
|
+
assert_equal users(:john), Credentials::Token.authenticate(john)
|
75
|
+
assert_equal users(:jane), Credentials::Token.authenticate(jane)
|
76
|
+
assert_equal :invalid, Credentials::Token.authenticate(bogus)
|
77
|
+
end
|
78
|
+
|
79
|
+
test 'class authenticate calls User#auth_bounce_reason' do
|
80
|
+
john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
|
81
|
+
jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
|
82
|
+
bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
|
83
|
+
|
84
|
+
with_blocked_credential credentials(:john_token), :reason do
|
85
|
+
assert_equal :reason, Credentials::Token.authenticate(john)
|
86
|
+
assert_equal users(:jane), Credentials::Token.authenticate(jane)
|
87
|
+
assert_equal :invalid, Credentials::Token.authenticate(bogus)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
test 'instance authenticate' do
|
92
|
+
assert_equal users(:john), credentials(:john_token).authenticate
|
93
|
+
assert_equal users(:jane), credentials(:jane_token).authenticate
|
94
|
+
end
|
95
|
+
|
96
|
+
test 'instance authenticate calls User#auth_bounce_reason' do
|
97
|
+
with_blocked_credential credentials(:john_token), :reason do
|
98
|
+
assert_equal :reason, credentials(:john_token).authenticate
|
99
|
+
assert_equal users(:jane), credentials(:jane_token).authenticate
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<p>
|
2
|
+
This view gets displayed when the user tries to access something forbidden.
|
3
|
+
</p>
|
4
|
+
|
5
|
+
<% if current_user %>
|
6
|
+
<p>
|
7
|
+
You should inform the user that they are logged in as
|
8
|
+
<%= current_user.exuid %> and suggest them to
|
9
|
+
<%= link_to 'Log out', session_path, :method => :destroy %> and log in as a
|
10
|
+
different user.
|
11
|
+
</p>
|
12
|
+
<% else %>
|
13
|
+
<p>
|
14
|
+
The user will only see this if JavaScript is disabled. Ask them to
|
15
|
+
<%= link_to 'Log in', new_session_path %>.
|
16
|
+
</p>
|
17
|
+
<script type="text/javascript">
|
18
|
+
window.location = "<%= new_session_path %>";
|
19
|
+
</script>
|
20
|
+
<% end %>
|