propel_authentication 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +290 -0
- data/Rakefile +12 -0
- data/lib/generators/propel_auth/install_generator.rb +486 -0
- data/lib/generators/propel_auth/pack_generator.rb +277 -0
- data/lib/generators/propel_auth/templates/agency.rb +7 -0
- data/lib/generators/propel_auth/templates/agent.rb +7 -0
- data/lib/generators/propel_auth/templates/auth/base_passwords_controller.rb.tt +99 -0
- data/lib/generators/propel_auth/templates/auth/base_tokens_controller.rb.tt +90 -0
- data/lib/generators/propel_auth/templates/auth/passwords_controller.rb.tt +126 -0
- data/lib/generators/propel_auth/templates/auth_mailer.rb +180 -0
- data/lib/generators/propel_auth/templates/authenticatable.rb +38 -0
- data/lib/generators/propel_auth/templates/concerns/confirmable.rb +145 -0
- data/lib/generators/propel_auth/templates/concerns/lockable.rb +123 -0
- data/lib/generators/propel_auth/templates/concerns/propel_authentication.rb +44 -0
- data/lib/generators/propel_auth/templates/concerns/rack_session_disable.rb +19 -0
- data/lib/generators/propel_auth/templates/concerns/recoverable.rb +124 -0
- data/lib/generators/propel_auth/templates/config/environments/development_email.rb +43 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_agencies.rb +20 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_agents.rb +11 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_invitations.rb +28 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_organizations.rb +18 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_users.rb +43 -0
- data/lib/generators/propel_auth/templates/db/seeds.rb +29 -0
- data/lib/generators/propel_auth/templates/invitation.rb +133 -0
- data/lib/generators/propel_auth/templates/lib/propel_auth.rb +84 -0
- data/lib/generators/propel_auth/templates/organization.rb +7 -0
- data/lib/generators/propel_auth/templates/propel_auth.rb +132 -0
- data/lib/generators/propel_auth/templates/services/auth_notification_service.rb +89 -0
- data/lib/generators/propel_auth/templates/test/concerns/confirmable_test.rb.tt +247 -0
- data/lib/generators/propel_auth/templates/test/concerns/lockable_test.rb.tt +282 -0
- data/lib/generators/propel_auth/templates/test/concerns/propel_authentication_test.rb.tt +75 -0
- data/lib/generators/propel_auth/templates/test/concerns/recoverable_test.rb.tt +327 -0
- data/lib/generators/propel_auth/templates/test/controllers/auth/lockable_integration_test.rb.tt +196 -0
- data/lib/generators/propel_auth/templates/test/controllers/auth/password_reset_integration_test.rb.tt +471 -0
- data/lib/generators/propel_auth/templates/test/controllers/auth/tokens_controller_test.rb.tt +265 -0
- data/lib/generators/propel_auth/templates/test/mailers/auth_mailer_test.rb.tt +216 -0
- data/lib/generators/propel_auth/templates/test/mailers/previews/auth_mailer_preview.rb +161 -0
- data/lib/generators/propel_auth/templates/tokens_controller.rb.tt +96 -0
- data/lib/generators/propel_auth/templates/user.rb +21 -0
- data/lib/generators/propel_auth/templates/user_test.rb.tt +81 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.html.erb +213 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.text.erb +56 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.html.erb +213 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.text.erb +32 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.html.erb +166 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.text.erb +32 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.html.erb +194 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.text.erb +51 -0
- data/lib/generators/propel_auth/test/dummy/Dockerfile +72 -0
- data/lib/generators/propel_auth/test/dummy/Gemfile +63 -0
- data/lib/generators/propel_auth/test/dummy/Gemfile.lock +394 -0
- data/lib/generators/propel_auth/test/dummy/README.md +24 -0
- data/lib/generators/propel_auth/test/dummy/Rakefile +6 -0
- data/lib/generators/propel_auth/test/dummy/app/assets/stylesheets/application.css +10 -0
- data/lib/generators/propel_auth/test/dummy/app/controllers/application_controller.rb +4 -0
- data/lib/generators/propel_auth/test/dummy/app/helpers/application_helper.rb +2 -0
- data/lib/generators/propel_auth/test/dummy/app/jobs/application_job.rb +7 -0
- data/lib/generators/propel_auth/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/lib/generators/propel_auth/test/dummy/app/models/application_record.rb +3 -0
- data/lib/generators/propel_auth/test/dummy/app/views/layouts/application.html.erb +27 -0
- data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/lib/generators/propel_auth/test/dummy/app/views/pwa/manifest.json.erb +22 -0
- data/lib/generators/propel_auth/test/dummy/app/views/pwa/service-worker.js +26 -0
- data/lib/generators/propel_auth/test/dummy/bin/brakeman +7 -0
- data/lib/generators/propel_auth/test/dummy/bin/dev +2 -0
- data/lib/generators/propel_auth/test/dummy/bin/docker-entrypoint +14 -0
- data/lib/generators/propel_auth/test/dummy/bin/rails +4 -0
- data/lib/generators/propel_auth/test/dummy/bin/rake +4 -0
- data/lib/generators/propel_auth/test/dummy/bin/rubocop +8 -0
- data/lib/generators/propel_auth/test/dummy/bin/setup +34 -0
- data/lib/generators/propel_auth/test/dummy/bin/thrust +5 -0
- data/lib/generators/propel_auth/test/dummy/config/application.rb +42 -0
- data/lib/generators/propel_auth/test/dummy/config/boot.rb +4 -0
- data/lib/generators/propel_auth/test/dummy/config/cable.yml +10 -0
- data/lib/generators/propel_auth/test/dummy/config/credentials.yml.enc +1 -0
- data/lib/generators/propel_auth/test/dummy/config/database.yml +41 -0
- data/lib/generators/propel_auth/test/dummy/config/environment.rb +5 -0
- data/lib/generators/propel_auth/test/dummy/config/environments/development.rb +72 -0
- data/lib/generators/propel_auth/test/dummy/config/environments/production.rb +89 -0
- data/lib/generators/propel_auth/test/dummy/config/environments/test.rb +53 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/assets.rb +10 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/content_security_policy.rb +25 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/inflections.rb +16 -0
- data/lib/generators/propel_auth/test/dummy/config/locales/en.yml +31 -0
- data/lib/generators/propel_auth/test/dummy/config/master.key +1 -0
- data/lib/generators/propel_auth/test/dummy/config/puma.rb +41 -0
- data/lib/generators/propel_auth/test/dummy/config/routes.rb +2 -0
- data/lib/generators/propel_auth/test/dummy/config/storage.yml +34 -0
- data/lib/generators/propel_auth/test/dummy/config.ru +6 -0
- data/lib/generators/propel_auth/test/dummy/db/schema.rb +14 -0
- data/lib/generators/propel_auth/test/generators/authentication/controllers/tokens_controller_test.rb +230 -0
- data/lib/generators/propel_auth/test/generators/authentication/install_generator_test.rb +490 -0
- data/lib/generators/propel_auth/test/generators/authentication/uninstall_generator_test.rb +408 -0
- data/lib/generators/propel_auth/test/integration/generator_integration_test.rb +158 -0
- data/lib/generators/propel_auth/test/integration/multi_version_generator_test.rb +125 -0
- data/lib/generators/propel_auth/unpack_generator.rb +345 -0
- data/lib/propel_auth.rb +3 -0
- metadata +195 -0
data/lib/generators/propel_auth/test/generators/authentication/controllers/tokens_controller_test.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TokensControllerTest < ActionDispatch::IntegrationTest
|
4
|
+
def setup
|
5
|
+
# Create test organization and user with real database
|
6
|
+
@organization = Organization.create!(
|
7
|
+
name: "Test Organization",
|
8
|
+
website: "https://test.com"
|
9
|
+
)
|
10
|
+
|
11
|
+
@user = User.create!(
|
12
|
+
email_address: "test@example.com",
|
13
|
+
username: "testuser",
|
14
|
+
password: "SecurePassword123!",
|
15
|
+
password_confirmation: "SecurePassword123!",
|
16
|
+
first_name: "Test",
|
17
|
+
last_name: "User",
|
18
|
+
organization: @organization
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
User.destroy_all
|
24
|
+
Organization.destroy_all
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_login_with_valid_credentials_returns_jwt_token
|
28
|
+
post "/auth/login", params: {
|
29
|
+
user: {
|
30
|
+
email_address: @user.email_address,
|
31
|
+
password: "SecurePassword123!"
|
32
|
+
}
|
33
|
+
}, as: :json
|
34
|
+
|
35
|
+
assert_response :ok
|
36
|
+
|
37
|
+
response_body = JSON.parse(response.body)
|
38
|
+
assert response_body.key?("token"), "Response should include JWT token"
|
39
|
+
assert response_body.key?("user"), "Response should include user data"
|
40
|
+
|
41
|
+
# Verify JWT token structure
|
42
|
+
token = response_body["token"]
|
43
|
+
assert token.is_a?(String), "Token should be a string"
|
44
|
+
assert token.split('.').length == 3, "JWT should have 3 parts separated by dots"
|
45
|
+
|
46
|
+
# Verify user data
|
47
|
+
user_data = response_body["user"]
|
48
|
+
assert_equal @user.email_address, user_data["email_address"], "Should return correct user email_address"
|
49
|
+
assert_equal @user.username, user_data["username"], "Should return correct username"
|
50
|
+
assert_nil user_data["password_digest"], "Should not expose password digest"
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_login_with_invalid_credentials_returns_unauthorized
|
54
|
+
post "/auth/login", params: {
|
55
|
+
user: {
|
56
|
+
email_address: @user.email_address,
|
57
|
+
password: "wrongpassword"
|
58
|
+
}
|
59
|
+
}, as: :json
|
60
|
+
|
61
|
+
assert_response :unauthorized
|
62
|
+
|
63
|
+
response_body = JSON.parse(response.body)
|
64
|
+
assert response_body.key?("error"), "Should return error message"
|
65
|
+
assert_equal "Invalid credentials", response_body["error"]
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_login_with_missing_email_returns_bad_request
|
69
|
+
post "/auth/login", params: {
|
70
|
+
user: {
|
71
|
+
password: "SecurePassword123!"
|
72
|
+
}
|
73
|
+
}, as: :json
|
74
|
+
|
75
|
+
assert_response :unprocessable_entity
|
76
|
+
|
77
|
+
response_body = JSON.parse(response.body)
|
78
|
+
assert response_body.key?("error"), "Should return error message"
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_me_endpoint_with_valid_jwt_returns_current_user
|
82
|
+
# First login to get token
|
83
|
+
post "/auth/login", params: {
|
84
|
+
user: {
|
85
|
+
email_address: @user.email_address,
|
86
|
+
password: "SecurePassword123!"
|
87
|
+
}
|
88
|
+
}, as: :json
|
89
|
+
|
90
|
+
token = JSON.parse(response.body)["token"]
|
91
|
+
|
92
|
+
# Then access protected endpoint
|
93
|
+
get "/auth/me", headers: {
|
94
|
+
"Authorization" => "Bearer #{token}"
|
95
|
+
}, as: :json
|
96
|
+
|
97
|
+
assert_response :ok
|
98
|
+
|
99
|
+
response_body = JSON.parse(response.body)
|
100
|
+
assert response_body.key?("user"), "Should return user data"
|
101
|
+
|
102
|
+
user_data = response_body["user"]
|
103
|
+
assert_equal @user.email_address, user_data["email_address"], "Should return correct user email_address"
|
104
|
+
assert_equal @user.id, user_data["id"], "Should return correct user ID"
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_me_endpoint_without_token_returns_unauthorized
|
108
|
+
get "/auth/me", as: :json
|
109
|
+
|
110
|
+
assert_response :unauthorized
|
111
|
+
|
112
|
+
response_body = JSON.parse(response.body)
|
113
|
+
assert response_body.key?("error"), "Should return error message"
|
114
|
+
assert_equal "No token provided", response_body["error"]
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_me_endpoint_with_invalid_token_returns_unauthorized
|
118
|
+
get "/auth/me", headers: {
|
119
|
+
"Authorization" => "Bearer invalid.jwt.token"
|
120
|
+
}, as: :json
|
121
|
+
|
122
|
+
assert_response :unauthorized
|
123
|
+
|
124
|
+
response_body = JSON.parse(response.body)
|
125
|
+
assert response_body.key?("error"), "Should return error message"
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_logout_with_valid_token_returns_success
|
129
|
+
# First login to get token
|
130
|
+
post "/auth/login", params: {
|
131
|
+
user: {
|
132
|
+
email_address: @user.email_address,
|
133
|
+
password: "SecurePassword123!"
|
134
|
+
}
|
135
|
+
}, as: :json
|
136
|
+
|
137
|
+
token = JSON.parse(response.body)["token"]
|
138
|
+
|
139
|
+
# Then logout
|
140
|
+
delete "/auth/logout", headers: {
|
141
|
+
"Authorization" => "Bearer #{token}"
|
142
|
+
}, as: :json
|
143
|
+
|
144
|
+
assert_response :ok
|
145
|
+
|
146
|
+
response_body = JSON.parse(response.body)
|
147
|
+
assert response_body.key?("message"), "Should return success message"
|
148
|
+
assert_equal "Logged out successfully", response_body["message"]
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_logout_without_token_returns_success
|
152
|
+
delete "/auth/logout", as: :json
|
153
|
+
|
154
|
+
assert_response :ok
|
155
|
+
|
156
|
+
response_body = JSON.parse(response.body)
|
157
|
+
assert response_body.key?("message"), "Should return success message"
|
158
|
+
assert_equal "Logged out successfully", response_body["message"]
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_jwt_token_includes_organization_context
|
162
|
+
post "/auth/login", params: {
|
163
|
+
user: {
|
164
|
+
email_address: @user.email_address,
|
165
|
+
password: "SecurePassword123!"
|
166
|
+
}
|
167
|
+
}, as: :json
|
168
|
+
|
169
|
+
token = JSON.parse(response.body)["token"]
|
170
|
+
|
171
|
+
# Decode JWT to verify organization context
|
172
|
+
payload = JWT.decode(token, PropelAccess.configuration.jwt_secret, true, { algorithm: 'HS256' })[0]
|
173
|
+
|
174
|
+
assert_equal @user.id, payload["user_id"], "Token should include user ID"
|
175
|
+
assert_equal @organization.id, payload["organization_id"], "Token should include organization ID"
|
176
|
+
assert payload.key?("exp"), "Token should include expiration"
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_expired_jwt_token_returns_unauthorized
|
180
|
+
# Create expired token
|
181
|
+
expired_payload = {
|
182
|
+
user_id: @user.id,
|
183
|
+
organization_id: @organization.id,
|
184
|
+
exp: 1.hour.ago.to_i
|
185
|
+
}
|
186
|
+
expired_token = JWT.encode(expired_payload, PropelAccess.configuration.jwt_secret, 'HS256')
|
187
|
+
|
188
|
+
get "/auth/me", headers: {
|
189
|
+
"Authorization" => "Bearer #{expired_token}"
|
190
|
+
}, as: :json
|
191
|
+
|
192
|
+
assert_response :unauthorized
|
193
|
+
|
194
|
+
response_body = JSON.parse(response.body)
|
195
|
+
assert response_body.key?("error"), "Should return error message"
|
196
|
+
assert_match(/expired/i, response_body["error"], "Error should mention token expiration")
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_jwt_authentication_workflow_end_to_end
|
200
|
+
# 1. Login with valid credentials
|
201
|
+
post "/auth/login", params: {
|
202
|
+
user: {
|
203
|
+
email_address: @user.email_address,
|
204
|
+
password: "SecurePassword123!"
|
205
|
+
}
|
206
|
+
}, as: :json
|
207
|
+
|
208
|
+
assert_response :ok
|
209
|
+
token = JSON.parse(response.body)["token"]
|
210
|
+
assert token.present?, "Login should return JWT token"
|
211
|
+
|
212
|
+
# 2. Access protected resource with token
|
213
|
+
get "/auth/me", headers: {
|
214
|
+
"Authorization" => "Bearer #{token}"
|
215
|
+
}, as: :json
|
216
|
+
|
217
|
+
assert_response :ok
|
218
|
+
user_data = JSON.parse(response.body)["user"]
|
219
|
+
assert_equal @user.email_address, user_data["email_address"], "Protected endpoint should return user data"
|
220
|
+
|
221
|
+
# 3. Logout
|
222
|
+
delete "/auth/logout", headers: {
|
223
|
+
"Authorization" => "Bearer #{token}"
|
224
|
+
}, as: :json
|
225
|
+
|
226
|
+
assert_response :ok
|
227
|
+
message = JSON.parse(response.body)["message"]
|
228
|
+
assert_equal "Logged out successfully", message, "Logout should return success message"
|
229
|
+
end
|
230
|
+
end
|