propel_authentication 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/README.md +6 -6
- data/lib/generators/propel_authentication/install_generator.rb +135 -153
- data/lib/generators/propel_authentication/templates/application_mailer.rb +6 -0
- data/lib/generators/propel_authentication/templates/auth/passwords_controller.rb.tt +84 -78
- data/lib/generators/propel_authentication/templates/auth/signup_controller.rb.tt +242 -0
- data/lib/generators/propel_authentication/templates/{tokens_controller.rb.tt → auth/tokens_controller.rb.tt} +39 -22
- data/lib/generators/propel_authentication/templates/auth_mailer.rb +3 -1
- data/lib/generators/propel_authentication/templates/authenticatable.rb +8 -2
- data/lib/generators/propel_authentication/templates/concerns/confirmable.rb +1 -1
- data/lib/generators/propel_authentication/templates/concerns/lockable.rb +4 -2
- data/lib/generators/propel_authentication/templates/concerns/{propel_authentication.rb → propel_authentication_concern.rb} +33 -3
- data/lib/generators/propel_authentication/templates/concerns/recoverable.rb +16 -6
- data/lib/generators/propel_authentication/templates/core/configuration_methods.rb +104 -64
- data/lib/generators/propel_authentication/templates/db/seeds.rb +50 -4
- data/lib/generators/propel_authentication/templates/doc/signup_flow.md +315 -0
- data/lib/generators/propel_authentication/templates/models/agency.rb.tt +13 -0
- data/lib/generators/propel_authentication/templates/models/agent.rb.tt +13 -0
- data/lib/generators/propel_authentication/templates/{invitation.rb → models/invitation.rb.tt} +6 -0
- data/lib/generators/propel_authentication/templates/models/organization.rb.tt +12 -0
- data/lib/generators/propel_authentication/templates/{user.rb → models/user.rb.tt} +5 -0
- data/lib/generators/propel_authentication/templates/propel_authentication.rb.tt +94 -9
- data/lib/generators/propel_authentication/templates/routes/auth_routes.rb.tt +55 -0
- data/lib/generators/propel_authentication/templates/services/auth_notification_service.rb +3 -3
- data/lib/generators/propel_authentication/templates/test/concerns/confirmable_test.rb.tt +34 -10
- data/lib/generators/propel_authentication/templates/test/concerns/propel_authentication_test.rb.tt +1 -1
- data/lib/generators/propel_authentication/templates/test/concerns/recoverable_test.rb.tt +4 -4
- data/lib/generators/propel_authentication/templates/test/controllers/auth/lockable_integration_test.rb.tt +18 -15
- data/lib/generators/propel_authentication/templates/test/controllers/auth/password_reset_integration_test.rb.tt +38 -40
- data/lib/generators/propel_authentication/templates/test/controllers/auth/signup_controller_test.rb.tt +201 -0
- data/lib/generators/propel_authentication/templates/test/controllers/auth/tokens_controller_test.rb.tt +33 -25
- data/lib/generators/propel_authentication/templates/test/mailers/auth_mailer_test.rb.tt +51 -36
- data/lib/generators/propel_authentication/templates/views/auth_mailer/email_confirmation.html.erb +2 -2
- data/lib/generators/propel_authentication/templates/views/auth_mailer/email_confirmation.text.erb +1 -1
- data/lib/generators/propel_authentication/test/generators/authentication/install_generator_test.rb +4 -4
- data/lib/generators/propel_authentication/test/generators/authentication/uninstall_generator_test.rb +1 -1
- data/lib/generators/propel_authentication/test/integration/generator_integration_test.rb +1 -1
- data/lib/generators/propel_authentication/test/integration/multi_version_generator_test.rb +13 -12
- data/lib/generators/propel_authentication/unpack_generator.rb +19 -15
- data/lib/propel_authentication.rb +1 -1
- metadata +14 -11
- data/lib/generators/propel_authentication/templates/agency.rb +0 -7
- data/lib/generators/propel_authentication/templates/agent.rb +0 -7
- data/lib/generators/propel_authentication/templates/auth/base_passwords_controller.rb.tt +0 -99
- data/lib/generators/propel_authentication/templates/auth/base_tokens_controller.rb.tt +0 -90
- data/lib/generators/propel_authentication/templates/organization.rb +0 -7
@@ -10,8 +10,8 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
10
10
|
|
11
11
|
test "should generate confirmation token on user creation" do
|
12
12
|
user = User.new(
|
13
|
-
email_address: '
|
14
|
-
username: '
|
13
|
+
email_address: 'test_token@example.com',
|
14
|
+
username: 'test_token_user',
|
15
15
|
password: 'securepassword123',
|
16
16
|
password_confirmation: 'securepassword123',
|
17
17
|
organization: @organization
|
@@ -23,7 +23,7 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
23
23
|
user.save!
|
24
24
|
|
25
25
|
assert_not_nil user.confirmation_token, "Confirmation token should be generated on save"
|
26
|
-
assert_not_nil user.confirmation_sent_at, "Confirmation sent at should be set
|
26
|
+
assert_not_nil user.confirmation_sent_at, "Confirmation sent at should be set after save due to after_create callback"
|
27
27
|
assert user.confirmation_token.length >= 32, "Confirmation token should be at least 32 characters"
|
28
28
|
end
|
29
29
|
|
@@ -114,8 +114,8 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
114
114
|
test "should send confirmation email on user creation" do
|
115
115
|
assert_emails 1 do
|
116
116
|
User.create!(
|
117
|
-
email_address: '
|
118
|
-
username: '
|
117
|
+
email_address: 'test_email_creation@example.com',
|
118
|
+
username: 'test_email_creation_user',
|
119
119
|
password: 'securepassword123',
|
120
120
|
password_confirmation: 'securepassword123',
|
121
121
|
organization: @organization
|
@@ -123,9 +123,21 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
123
123
|
end
|
124
124
|
|
125
125
|
email = ActionMailer::Base.deliveries.last
|
126
|
-
assert_equal '
|
126
|
+
assert_equal 'test_email_creation@example.com', email.to.first, "Email should be sent to user's email address"
|
127
127
|
assert_match(/confirm/i, email.subject, "Email subject should mention confirmation")
|
128
|
-
|
128
|
+
|
129
|
+
# Test multipart email content properly
|
130
|
+
if email.multipart?
|
131
|
+
text_part = email.body.parts.find { |part| part.content_type.include?('text/plain') }
|
132
|
+
html_part = email.body.parts.find { |part| part.content_type.include?('text/html') }
|
133
|
+
|
134
|
+
assert_not_nil text_part, "Email should have text part"
|
135
|
+
assert_not_nil html_part, "Email should have HTML part"
|
136
|
+
assert_match(/confirm/i, text_part.body.to_s, "Text part should contain confirmation instructions")
|
137
|
+
assert_match(/confirm/i, html_part.body.to_s, "HTML part should contain confirmation instructions")
|
138
|
+
else
|
139
|
+
assert_match(/confirm/i, email.body.to_s, "Email body should contain confirmation instructions")
|
140
|
+
end
|
129
141
|
end
|
130
142
|
|
131
143
|
test "should send confirmation email when resending instructions" do
|
@@ -137,7 +149,18 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
137
149
|
|
138
150
|
email = ActionMailer::Base.deliveries.last
|
139
151
|
assert_equal @user.email_address, email.to.first, "Email should be sent to user's email address"
|
140
|
-
|
152
|
+
|
153
|
+
# Test multipart email content properly for confirmation token
|
154
|
+
if email.multipart?
|
155
|
+
text_part = email.body.parts.find { |part| part.content_type.include?('text/plain') }
|
156
|
+
html_part = email.body.parts.find { |part| part.content_type.include?('text/html') }
|
157
|
+
|
158
|
+
assert_not_nil text_part, "Email should have text part"
|
159
|
+
assert_match(@user.confirmation_token, text_part.body.to_s, "Text part should contain confirmation token")
|
160
|
+
assert_match(@user.confirmation_token, html_part.body.to_s, "HTML part should contain confirmation token")
|
161
|
+
else
|
162
|
+
assert_match(@user.confirmation_token, email.body.to_s, "Email should contain confirmation token")
|
163
|
+
end
|
141
164
|
end
|
142
165
|
|
143
166
|
test "should not send confirmation email to already confirmed users" do
|
@@ -177,12 +200,13 @@ class ConfirmableTest < ActiveSupport::TestCase
|
|
177
200
|
original_email = @user.email_address
|
178
201
|
|
179
202
|
assert_emails 1 do
|
180
|
-
@user.update!(email_address: '
|
203
|
+
@user.update!(email_address: 'test_email_change@example.com')
|
181
204
|
end
|
182
205
|
|
183
206
|
@user.reload
|
184
207
|
assert_not @user.confirmed?, "Should require reconfirmation after email change"
|
185
|
-
assert_equal
|
208
|
+
assert_equal original_email, @user.email_address, "Email should remain original until confirmed"
|
209
|
+
assert_equal 'test_email_change@example.com', @user.unconfirmed_email_address, "New email should be stored for confirmation"
|
186
210
|
assert_not_nil @user.confirmation_token, "Should generate new confirmation token"
|
187
211
|
end
|
188
212
|
|
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class RecoverableTest < ActiveSupport::TestCase
|
4
4
|
def setup
|
5
|
-
@user = users(:
|
5
|
+
@user = users(:confirmed_user)
|
6
6
|
@organization = @user.organization
|
7
7
|
end
|
8
8
|
|
@@ -234,7 +234,7 @@ class RecoverableTest < ActiveSupport::TestCase
|
|
234
234
|
reset_token = @user.generate_password_reset_token
|
235
235
|
|
236
236
|
# EXECUTE LOOKUP: Find user by reset token
|
237
|
-
found_user = User.
|
237
|
+
found_user = User.find_by_jwt_password_reset_token(reset_token)
|
238
238
|
|
239
239
|
# VERIFY LOOKUP: Should find correct user
|
240
240
|
assert_equal @user.id, found_user.id, "Should find user by valid reset token"
|
@@ -247,7 +247,7 @@ class RecoverableTest < ActiveSupport::TestCase
|
|
247
247
|
invalid_token = "invalid.jwt.token"
|
248
248
|
|
249
249
|
# EXECUTE LOOKUP: Try to find user with invalid token
|
250
|
-
found_user = User.
|
250
|
+
found_user = User.find_by_jwt_password_reset_token(invalid_token)
|
251
251
|
|
252
252
|
# VERIFY REJECTION: Should return nil
|
253
253
|
assert_nil found_user, "Should return nil for invalid token"
|
@@ -267,7 +267,7 @@ class RecoverableTest < ActiveSupport::TestCase
|
|
267
267
|
expired_token = JWT.encode(expired_payload, PropelAuthentication.configuration.jwt_secret, 'HS256')
|
268
268
|
|
269
269
|
# EXECUTE LOOKUP: Try to find user with expired token
|
270
|
-
found_user = User.
|
270
|
+
found_user = User.find_by_jwt_password_reset_token(expired_token)
|
271
271
|
|
272
272
|
# VERIFY EXPIRATION: Should return nil
|
273
273
|
assert_nil found_user, "Should return nil for expired token"
|
@@ -1,14 +1,17 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class <%= controller_namespace
|
3
|
+
class <%= controller_namespace('tokens') %>LockableIntegrationTest < ActionDispatch::IntegrationTest
|
4
4
|
def setup
|
5
5
|
@organization = Organization.create!(name: "Test Organization")
|
6
|
+
@agency = Agency.create!(name: "Test Agency", organization: @organization)
|
6
7
|
@user = User.create!(
|
7
8
|
email_address: "lockable-integration@example.com",
|
8
9
|
username: "lockableintegration",
|
9
10
|
password: "correctpassword123",
|
10
11
|
organization: @organization
|
11
12
|
)
|
13
|
+
# Create agent association for agency access (required for real-time agency lookup)
|
14
|
+
Agent.create!(user: @user, agency: @agency, role: "member")
|
12
15
|
end
|
13
16
|
|
14
17
|
# CRITICAL: Real API integration with lockable functionality
|
@@ -16,7 +19,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
16
19
|
test "should track failed attempts during actual login API calls" do
|
17
20
|
# BEHAVIOR: Verify failed login attempts are tracked through real API
|
18
21
|
9.times do |i|
|
19
|
-
post <%=
|
22
|
+
post '<%= auth_route_prefix %>/login', params: {
|
20
23
|
user: { email_address: @user.email_address, password: "wrongpassword" }
|
21
24
|
}
|
22
25
|
|
@@ -32,14 +35,14 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
32
35
|
|
33
36
|
# First 9 attempts should not lock
|
34
37
|
9.times do
|
35
|
-
post <%=
|
38
|
+
post '<%= auth_route_prefix %>/login', params: {
|
36
39
|
user: { email_address: @user.email_address, password: "wrongpassword" }
|
37
40
|
}
|
38
41
|
assert_response :unauthorized
|
39
42
|
end
|
40
43
|
|
41
44
|
# 10th attempt should lock the account
|
42
|
-
post <%=
|
45
|
+
post '<%= auth_route_prefix %>/login', params: {
|
43
46
|
user: { email_address: @user.email_address, password: "wrongpassword" }
|
44
47
|
}
|
45
48
|
|
@@ -52,14 +55,14 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
52
55
|
# BEHAVIOR: Verify locked accounts return proper HTTP status and error
|
53
56
|
@user.lock_account!
|
54
57
|
|
55
|
-
post <%=
|
58
|
+
post '<%= auth_route_prefix %>/login', params: {
|
56
59
|
user: { email_address: @user.email_address, password: "correctpassword123" }
|
57
60
|
}
|
58
61
|
|
59
62
|
assert_response :locked, "Should return 423 locked status"
|
60
63
|
|
61
64
|
json = JSON.parse(response.body)
|
62
|
-
assert_match(/locked.*too many
|
65
|
+
assert_match(/locked.*too many.*failed.*attempts/i, json['error'], "Should return descriptive error message")
|
63
66
|
assert_nil json['token'], "Should not return authentication token"
|
64
67
|
assert_nil json['user'], "Should not return user data"
|
65
68
|
end
|
@@ -69,7 +72,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
69
72
|
@user.lock_account!
|
70
73
|
|
71
74
|
# Try with correct password
|
72
|
-
post <%=
|
75
|
+
post '<%= auth_route_prefix %>/login', params: {
|
73
76
|
user: { email_address: @user.email_address, password: "correctpassword123" }
|
74
77
|
}
|
75
78
|
|
@@ -85,7 +88,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
85
88
|
|
86
89
|
# Build up some failed attempts
|
87
90
|
5.times do
|
88
|
-
post <%=
|
91
|
+
post '<%= auth_route_prefix %>/login', params: {
|
89
92
|
user: { email_address: @user.email_address, password: "wrongpassword" }
|
90
93
|
}
|
91
94
|
end
|
@@ -94,7 +97,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
94
97
|
assert_equal 5, @user.failed_login_attempts, "Should have 5 failed attempts"
|
95
98
|
|
96
99
|
# Successful login should reset counter
|
97
|
-
post <%=
|
100
|
+
post '<%= auth_route_prefix %>/login', params: {
|
98
101
|
user: { email_address: @user.email_address, password: "correctpassword123" }
|
99
102
|
}
|
100
103
|
|
@@ -111,7 +114,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
111
114
|
@user.lock_account!
|
112
115
|
unlock_token = @user.generate_unlock_token
|
113
116
|
|
114
|
-
post <%=
|
117
|
+
post '<%= auth_route_prefix %>/unlock', params: { token: unlock_token }
|
115
118
|
|
116
119
|
assert_response :ok, "Valid unlock token should return 200"
|
117
120
|
|
@@ -128,7 +131,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
128
131
|
# API INTEGRATION: Verify unlock endpoint properly validates tokens
|
129
132
|
@user.lock_account!
|
130
133
|
|
131
|
-
post <%=
|
134
|
+
post '<%= auth_route_prefix %>/unlock', params: { token: "invalid_token_12345" }
|
132
135
|
|
133
136
|
assert_response :unauthorized, "Invalid token should return 401"
|
134
137
|
|
@@ -143,7 +146,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
143
146
|
# API VALIDATION: Verify unlock endpoint validates required parameters
|
144
147
|
@user.lock_account!
|
145
148
|
|
146
|
-
post <%=
|
149
|
+
post '<%= auth_route_prefix %>/unlock', params: {}
|
147
150
|
|
148
151
|
assert_response :unprocessable_entity, "Missing token should return 422"
|
149
152
|
|
@@ -153,7 +156,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
153
156
|
|
154
157
|
test "should not increment attempts when user does not exist" do
|
155
158
|
# SECURITY: Verify failed attempts are not tracked for nonexistent users
|
156
|
-
post <%=
|
159
|
+
post '<%= auth_route_prefix %>/login', params: {
|
157
160
|
user: { email_address: "nonexistent@example.com", password: "anypassword" }
|
158
161
|
}
|
159
162
|
|
@@ -166,7 +169,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
166
169
|
test "should handle graceful error responses for authentication failures" do
|
167
170
|
# ERROR HANDLING: Verify system handles authentication failures gracefully
|
168
171
|
|
169
|
-
post <%=
|
172
|
+
post '<%= auth_route_prefix %>/login', params: {
|
170
173
|
user: { email_address: "nonexistent@example.com", password: "wrongpassword" }
|
171
174
|
}
|
172
175
|
|
@@ -182,7 +185,7 @@ class <%= controller_namespace %>::LockableIntegrationTest < ActionDispatch::Int
|
|
182
185
|
# BEHAVIOR: Verify login timestamp tracking
|
183
186
|
start_time = Time.current
|
184
187
|
|
185
|
-
post <%=
|
188
|
+
post '<%= auth_route_prefix %>/login', params: {
|
186
189
|
user: { email_address: @user.email_address, password: "correctpassword123" }
|
187
190
|
}
|
188
191
|
|
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class <%= controller_namespace
|
3
|
+
class <%= controller_namespace('passwords') %>PasswordResetIntegrationTest < ActionDispatch::IntegrationTest
|
4
4
|
def setup
|
5
5
|
@organization = Organization.create!(name: "Test Organization")
|
6
|
+
@agency = Agency.create!(name: "Test Agency", organization: @organization)
|
6
7
|
@user = User.create!(
|
7
8
|
email_address: "password-reset@example.com",
|
8
9
|
username: "passwordresetuser",
|
@@ -11,18 +12,15 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
11
12
|
first_name: "Password",
|
12
13
|
last_name: "User"
|
13
14
|
)
|
15
|
+
# Create agent association for agency access (required for real-time agency lookup)
|
16
|
+
Agent.create!(user: @user, agency: @agency, role: "member")
|
14
17
|
end
|
15
18
|
|
16
|
-
|
17
|
-
User.destroy_all
|
18
|
-
Organization.destroy_all
|
19
|
-
end
|
20
|
-
|
21
|
-
# POST <%= api_versioned? ? "/#{api_namespace}" : "" %>/auth/reset - Request Password Reset Tests
|
19
|
+
# POST <%= auth_route_prefix %>/reset - Request Password Reset Tests
|
22
20
|
test "should initiate password reset with valid email_address" do
|
23
21
|
# BEHAVIOR: Verify password reset request works with valid email_address
|
24
22
|
|
25
|
-
post <%=
|
23
|
+
post '<%= auth_route_prefix %>/reset', params: { email_address: @user.email_address }
|
26
24
|
|
27
25
|
# VERIFY API RESPONSE: Should return success status
|
28
26
|
assert_response :ok, "Valid email_address should initiate password reset"
|
@@ -38,7 +36,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
38
36
|
test "should reject password reset with invalid email_address format" do
|
39
37
|
# VALIDATION: Verify email_address format validation
|
40
38
|
|
41
|
-
post <%=
|
39
|
+
post '<%= auth_route_prefix %>/reset', params: { email_address: "invalid-email-format" }
|
42
40
|
|
43
41
|
# VERIFY VALIDATION ERROR: Should return validation error
|
44
42
|
assert_response :unprocessable_entity, "Invalid email_address format should be rejected"
|
@@ -50,7 +48,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
50
48
|
test "should handle password reset for nonexistent email_address safely" do
|
51
49
|
# SECURITY: Verify nonexistent email_addresses don't reveal user information
|
52
50
|
|
53
|
-
post <%=
|
51
|
+
post '<%= auth_route_prefix %>/reset', params: { email_address: "nonexistent@example.com" }
|
54
52
|
|
55
53
|
# VERIFY SECURITY RESPONSE: Should return same response as valid email_address (prevent enumeration)
|
56
54
|
assert_response :ok, "Nonexistent email_address should return same response as valid email_address"
|
@@ -64,7 +62,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
64
62
|
test "should require email_address parameter for password reset request" do
|
65
63
|
# VALIDATION: Verify email_address parameter is required
|
66
64
|
|
67
|
-
post <%=
|
65
|
+
post '<%= auth_route_prefix %>/reset', params: {}
|
68
66
|
|
69
67
|
# VERIFY PARAMETER VALIDATION: Should return parameter error
|
70
68
|
assert_response :unprocessable_entity, "Missing email_address should return validation error"
|
@@ -78,18 +76,18 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
78
76
|
|
79
77
|
# EXECUTE MULTIPLE REQUESTS: Send multiple reset requests rapidly
|
80
78
|
5.times do
|
81
|
-
post <%=
|
79
|
+
post '<%= auth_route_prefix %>/reset', params: { email_address: @user.email_address }
|
82
80
|
end
|
83
81
|
|
84
82
|
# Make one more request that should be rate limited
|
85
|
-
post <%=
|
83
|
+
post '<%= auth_route_prefix %>/reset', params: { email_address: @user.email_address }
|
86
84
|
|
87
85
|
# VERIFY RATE LIMITING: Should eventually rate limit (implementation dependent)
|
88
86
|
# For now, verify the endpoint is accessible (rate limiting will be added later)
|
89
87
|
assert_response :ok, "Rate limiting test - endpoint should be accessible"
|
90
88
|
end
|
91
89
|
|
92
|
-
# PUT <%=
|
90
|
+
# PUT <%= auth_route_prefix %>/reset - Confirm Password Reset Tests
|
93
91
|
test "should reset password with valid token and new password" do
|
94
92
|
# BEHAVIOR: Verify password reset confirmation works end-to-end
|
95
93
|
|
@@ -98,7 +96,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
98
96
|
new_password = "newpassword456"
|
99
97
|
|
100
98
|
# EXECUTE RESET: Confirm password reset with token
|
101
|
-
|
99
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
102
100
|
token: reset_token,
|
103
101
|
password: new_password,
|
104
102
|
password_confirmation: new_password
|
@@ -125,7 +123,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
125
123
|
new_password = "attemptedpassword"
|
126
124
|
|
127
125
|
# EXECUTE WITH INVALID TOKEN: Try to reset with invalid token
|
128
|
-
|
126
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
129
127
|
token: invalid_token,
|
130
128
|
password: new_password,
|
131
129
|
password_confirmation: new_password
|
@@ -154,10 +152,10 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
154
152
|
exp: 1.second.ago.to_i, # Already expired
|
155
153
|
password_hash: @user.password_digest[0..10]
|
156
154
|
}
|
157
|
-
expired_token = JWT.encode(expired_payload,
|
155
|
+
expired_token = JWT.encode(expired_payload, PropelAuthentication.configuration.jwt_secret, 'HS256')
|
158
156
|
|
159
157
|
# EXECUTE WITH EXPIRED TOKEN: Try to reset with expired token
|
160
|
-
|
158
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
161
159
|
token: expired_token,
|
162
160
|
password: "newpassword",
|
163
161
|
password_confirmation: "newpassword"
|
@@ -176,7 +174,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
176
174
|
reset_token = @user.generate_password_reset_token
|
177
175
|
|
178
176
|
# EXECUTE WITH MISMATCHED PASSWORDS: Password and confirmation don't match
|
179
|
-
|
177
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
180
178
|
token: reset_token,
|
181
179
|
password: "newpassword123",
|
182
180
|
password_confirmation: "differentpassword456"
|
@@ -200,7 +198,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
200
198
|
weak_password = "123" # Too short
|
201
199
|
|
202
200
|
# EXECUTE WITH WEAK PASSWORD: Try to set weak password
|
203
|
-
|
201
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
204
202
|
token: reset_token,
|
205
203
|
password: weak_password,
|
206
204
|
password_confirmation: weak_password
|
@@ -223,7 +221,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
223
221
|
reset_token = @user.generate_password_reset_token
|
224
222
|
|
225
223
|
# TEST MISSING TOKEN
|
226
|
-
|
224
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
227
225
|
password: "newpassword123",
|
228
226
|
password_confirmation: "newpassword123"
|
229
227
|
}
|
@@ -231,7 +229,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
231
229
|
assert_response :unprocessable_entity, "Missing token should be rejected"
|
232
230
|
|
233
231
|
# TEST MISSING PASSWORD
|
234
|
-
|
232
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
235
233
|
token: reset_token,
|
236
234
|
password_confirmation: "newpassword123"
|
237
235
|
}
|
@@ -239,7 +237,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
239
237
|
assert_response :unprocessable_entity, "Missing password should be rejected"
|
240
238
|
|
241
239
|
# TEST MISSING CONFIRMATION
|
242
|
-
|
240
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
243
241
|
token: reset_token,
|
244
242
|
password: "newpassword123"
|
245
243
|
}
|
@@ -256,7 +254,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
256
254
|
|
257
255
|
# EXECUTE RESET: Reset password successfully
|
258
256
|
reset_token = @user.generate_password_reset_token
|
259
|
-
|
257
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
260
258
|
token: reset_token,
|
261
259
|
password: "newpassword123",
|
262
260
|
password_confirmation: "newpassword123"
|
@@ -279,7 +277,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
279
277
|
|
280
278
|
# EXECUTE RESET: Reset password while locked
|
281
279
|
reset_token = @user.generate_password_reset_token
|
282
|
-
|
280
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
283
281
|
token: reset_token,
|
284
282
|
password: "newpassword123",
|
285
283
|
password_confirmation: "newpassword123"
|
@@ -294,14 +292,14 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
294
292
|
assert @user.authenticate("newpassword123"), "Should authenticate with new password"
|
295
293
|
end
|
296
294
|
|
297
|
-
# GET <%=
|
295
|
+
# GET <%= auth_route_prefix %>/reset/verify - Token Verification Tests
|
298
296
|
test "should verify valid password reset token" do
|
299
297
|
# BEHAVIOR: Verify token verification endpoint works
|
300
298
|
|
301
299
|
reset_token = @user.generate_password_reset_token
|
302
300
|
|
303
301
|
# EXECUTE VERIFICATION: Verify token is valid
|
304
|
-
get <%=
|
302
|
+
get '<%= auth_route_prefix %>/reset', params: { token: reset_token }
|
305
303
|
|
306
304
|
# VERIFY API RESPONSE: Should confirm token validity
|
307
305
|
assert_response :ok, "Valid token should pass verification"
|
@@ -319,7 +317,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
319
317
|
invalid_token = "invalid.jwt.token"
|
320
318
|
|
321
319
|
# EXECUTE VERIFICATION: Verify invalid token
|
322
|
-
get <%=
|
320
|
+
get '<%= auth_route_prefix %>/reset', params: { token: invalid_token }
|
323
321
|
|
324
322
|
# VERIFY REJECTION: Should indicate token is invalid
|
325
323
|
assert_response :unauthorized, "Invalid token should be rejected"
|
@@ -340,10 +338,10 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
340
338
|
exp: 1.second.ago.to_i, # Already expired
|
341
339
|
password_hash: @user.password_digest[0..10]
|
342
340
|
}
|
343
|
-
expired_token = JWT.encode(expired_payload,
|
341
|
+
expired_token = JWT.encode(expired_payload, PropelAuthentication.configuration.jwt_secret, 'HS256')
|
344
342
|
|
345
343
|
# EXECUTE VERIFICATION: Verify expired token
|
346
|
-
get <%=
|
344
|
+
get '<%= auth_route_prefix %>/reset', params: { token: expired_token }
|
347
345
|
|
348
346
|
# VERIFY EXPIRATION: Should indicate token is expired
|
349
347
|
assert_response :unauthorized, "Expired token should be rejected"
|
@@ -355,7 +353,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
355
353
|
test "should require token parameter for verification" do
|
356
354
|
# VALIDATION: Verify token parameter is required for verification
|
357
355
|
|
358
|
-
get <%=
|
356
|
+
get '<%= auth_route_prefix %>/reset', params: {}
|
359
357
|
|
360
358
|
# VERIFY PARAMETER VALIDATION: Should return parameter error
|
361
359
|
assert_response :unprocessable_entity, "Missing token should return validation error"
|
@@ -369,12 +367,12 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
369
367
|
# INTEGRATION: Test complete password reset workflow from start to finish
|
370
368
|
|
371
369
|
# STEP 1: REQUEST RESET - Send password reset request
|
372
|
-
post <%=
|
370
|
+
post '<%= auth_route_prefix %>/reset', params: { email_address: @user.email_address }
|
373
371
|
assert_response :ok, "Reset request should succeed"
|
374
372
|
|
375
373
|
# STEP 2: VERIFY TOKEN - Check that we can verify tokens (simulate email link click)
|
376
374
|
reset_token = @user.generate_password_reset_token # Simulate token from email
|
377
|
-
get <%=
|
375
|
+
get '<%= auth_route_prefix %>/reset', params: { token: reset_token }
|
378
376
|
assert_response :ok, "Token verification should succeed"
|
379
377
|
|
380
378
|
verification_json = JSON.parse(response.body)
|
@@ -382,7 +380,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
382
380
|
|
383
381
|
# STEP 3: RESET PASSWORD - Complete password reset
|
384
382
|
new_password = "completeworkflow123"
|
385
|
-
|
383
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
386
384
|
token: reset_token,
|
387
385
|
password: new_password,
|
388
386
|
password_confirmation: new_password
|
@@ -390,7 +388,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
390
388
|
assert_response :ok, "Password reset should succeed"
|
391
389
|
|
392
390
|
# STEP 4: VERIFY NEW PASSWORD - Test login with new password
|
393
|
-
post <%=
|
391
|
+
post '<%= auth_route_prefix %>/login', params: {
|
394
392
|
user: { email_address: @user.email_address, password: new_password }
|
395
393
|
}
|
396
394
|
assert_response :ok, "Should login with new password"
|
@@ -399,7 +397,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
399
397
|
assert login_json['token'].present?, "Should receive JWT token"
|
400
398
|
|
401
399
|
# STEP 5: VERIFY OLD PASSWORD INVALID - Test old password doesn't work
|
402
|
-
post <%=
|
400
|
+
post '<%= auth_route_prefix %>/login', params: {
|
403
401
|
user: { email_address: @user.email_address, password: "originalpassword123" }
|
404
402
|
}
|
405
403
|
assert_response :unauthorized, "Should not login with old password"
|
@@ -411,7 +409,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
411
409
|
reset_token = @user.generate_password_reset_token
|
412
410
|
|
413
411
|
# FIRST RESET: Use token successfully
|
414
|
-
|
412
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
415
413
|
token: reset_token,
|
416
414
|
password: "firstnewpassword123",
|
417
415
|
password_confirmation: "firstnewpassword123"
|
@@ -419,7 +417,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
419
417
|
assert_response :ok, "First password reset should succeed"
|
420
418
|
|
421
419
|
# ATTEMPTED REUSE: Try to use same token again
|
422
|
-
|
420
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
423
421
|
token: reset_token,
|
424
422
|
password: "secondnewpassword456",
|
425
423
|
password_confirmation: "secondnewpassword456"
|
@@ -447,7 +445,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
447
445
|
assert @user.valid_password_reset_token?(token2), "Second token should be valid"
|
448
446
|
|
449
447
|
# USE FIRST TOKEN: Reset password with first token
|
450
|
-
|
448
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
451
449
|
token: token1,
|
452
450
|
password: "concurrentpassword1",
|
453
451
|
password_confirmation: "concurrentpassword1"
|
@@ -455,7 +453,7 @@ class <%= controller_namespace %>::PasswordResetIntegrationTest < ActionDispatch
|
|
455
453
|
assert_response :ok, "First reset should succeed"
|
456
454
|
|
457
455
|
# TRY SECOND TOKEN: Attempt reset with second token (should fail due to password change)
|
458
|
-
|
456
|
+
patch '<%= auth_route_prefix %>/reset', params: {
|
459
457
|
token: token2,
|
460
458
|
password: "concurrentpassword2",
|
461
459
|
password_confirmation: "concurrentpassword2"
|