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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +290 -0
  4. data/Rakefile +12 -0
  5. data/lib/generators/propel_auth/install_generator.rb +486 -0
  6. data/lib/generators/propel_auth/pack_generator.rb +277 -0
  7. data/lib/generators/propel_auth/templates/agency.rb +7 -0
  8. data/lib/generators/propel_auth/templates/agent.rb +7 -0
  9. data/lib/generators/propel_auth/templates/auth/base_passwords_controller.rb.tt +99 -0
  10. data/lib/generators/propel_auth/templates/auth/base_tokens_controller.rb.tt +90 -0
  11. data/lib/generators/propel_auth/templates/auth/passwords_controller.rb.tt +126 -0
  12. data/lib/generators/propel_auth/templates/auth_mailer.rb +180 -0
  13. data/lib/generators/propel_auth/templates/authenticatable.rb +38 -0
  14. data/lib/generators/propel_auth/templates/concerns/confirmable.rb +145 -0
  15. data/lib/generators/propel_auth/templates/concerns/lockable.rb +123 -0
  16. data/lib/generators/propel_auth/templates/concerns/propel_authentication.rb +44 -0
  17. data/lib/generators/propel_auth/templates/concerns/rack_session_disable.rb +19 -0
  18. data/lib/generators/propel_auth/templates/concerns/recoverable.rb +124 -0
  19. data/lib/generators/propel_auth/templates/config/environments/development_email.rb +43 -0
  20. data/lib/generators/propel_auth/templates/db/migrate/create_agencies.rb +20 -0
  21. data/lib/generators/propel_auth/templates/db/migrate/create_agents.rb +11 -0
  22. data/lib/generators/propel_auth/templates/db/migrate/create_invitations.rb +28 -0
  23. data/lib/generators/propel_auth/templates/db/migrate/create_organizations.rb +18 -0
  24. data/lib/generators/propel_auth/templates/db/migrate/create_users.rb +43 -0
  25. data/lib/generators/propel_auth/templates/db/seeds.rb +29 -0
  26. data/lib/generators/propel_auth/templates/invitation.rb +133 -0
  27. data/lib/generators/propel_auth/templates/lib/propel_auth.rb +84 -0
  28. data/lib/generators/propel_auth/templates/organization.rb +7 -0
  29. data/lib/generators/propel_auth/templates/propel_auth.rb +132 -0
  30. data/lib/generators/propel_auth/templates/services/auth_notification_service.rb +89 -0
  31. data/lib/generators/propel_auth/templates/test/concerns/confirmable_test.rb.tt +247 -0
  32. data/lib/generators/propel_auth/templates/test/concerns/lockable_test.rb.tt +282 -0
  33. data/lib/generators/propel_auth/templates/test/concerns/propel_authentication_test.rb.tt +75 -0
  34. data/lib/generators/propel_auth/templates/test/concerns/recoverable_test.rb.tt +327 -0
  35. data/lib/generators/propel_auth/templates/test/controllers/auth/lockable_integration_test.rb.tt +196 -0
  36. data/lib/generators/propel_auth/templates/test/controllers/auth/password_reset_integration_test.rb.tt +471 -0
  37. data/lib/generators/propel_auth/templates/test/controllers/auth/tokens_controller_test.rb.tt +265 -0
  38. data/lib/generators/propel_auth/templates/test/mailers/auth_mailer_test.rb.tt +216 -0
  39. data/lib/generators/propel_auth/templates/test/mailers/previews/auth_mailer_preview.rb +161 -0
  40. data/lib/generators/propel_auth/templates/tokens_controller.rb.tt +96 -0
  41. data/lib/generators/propel_auth/templates/user.rb +21 -0
  42. data/lib/generators/propel_auth/templates/user_test.rb.tt +81 -0
  43. data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.html.erb +213 -0
  44. data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.text.erb +56 -0
  45. data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.html.erb +213 -0
  46. data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.text.erb +32 -0
  47. data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.html.erb +166 -0
  48. data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.text.erb +32 -0
  49. data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.html.erb +194 -0
  50. data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.text.erb +51 -0
  51. data/lib/generators/propel_auth/test/dummy/Dockerfile +72 -0
  52. data/lib/generators/propel_auth/test/dummy/Gemfile +63 -0
  53. data/lib/generators/propel_auth/test/dummy/Gemfile.lock +394 -0
  54. data/lib/generators/propel_auth/test/dummy/README.md +24 -0
  55. data/lib/generators/propel_auth/test/dummy/Rakefile +6 -0
  56. data/lib/generators/propel_auth/test/dummy/app/assets/stylesheets/application.css +10 -0
  57. data/lib/generators/propel_auth/test/dummy/app/controllers/application_controller.rb +4 -0
  58. data/lib/generators/propel_auth/test/dummy/app/helpers/application_helper.rb +2 -0
  59. data/lib/generators/propel_auth/test/dummy/app/jobs/application_job.rb +7 -0
  60. data/lib/generators/propel_auth/test/dummy/app/mailers/application_mailer.rb +4 -0
  61. data/lib/generators/propel_auth/test/dummy/app/models/application_record.rb +3 -0
  62. data/lib/generators/propel_auth/test/dummy/app/views/layouts/application.html.erb +27 -0
  63. data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  64. data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  65. data/lib/generators/propel_auth/test/dummy/app/views/pwa/manifest.json.erb +22 -0
  66. data/lib/generators/propel_auth/test/dummy/app/views/pwa/service-worker.js +26 -0
  67. data/lib/generators/propel_auth/test/dummy/bin/brakeman +7 -0
  68. data/lib/generators/propel_auth/test/dummy/bin/dev +2 -0
  69. data/lib/generators/propel_auth/test/dummy/bin/docker-entrypoint +14 -0
  70. data/lib/generators/propel_auth/test/dummy/bin/rails +4 -0
  71. data/lib/generators/propel_auth/test/dummy/bin/rake +4 -0
  72. data/lib/generators/propel_auth/test/dummy/bin/rubocop +8 -0
  73. data/lib/generators/propel_auth/test/dummy/bin/setup +34 -0
  74. data/lib/generators/propel_auth/test/dummy/bin/thrust +5 -0
  75. data/lib/generators/propel_auth/test/dummy/config/application.rb +42 -0
  76. data/lib/generators/propel_auth/test/dummy/config/boot.rb +4 -0
  77. data/lib/generators/propel_auth/test/dummy/config/cable.yml +10 -0
  78. data/lib/generators/propel_auth/test/dummy/config/credentials.yml.enc +1 -0
  79. data/lib/generators/propel_auth/test/dummy/config/database.yml +41 -0
  80. data/lib/generators/propel_auth/test/dummy/config/environment.rb +5 -0
  81. data/lib/generators/propel_auth/test/dummy/config/environments/development.rb +72 -0
  82. data/lib/generators/propel_auth/test/dummy/config/environments/production.rb +89 -0
  83. data/lib/generators/propel_auth/test/dummy/config/environments/test.rb +53 -0
  84. data/lib/generators/propel_auth/test/dummy/config/initializers/assets.rb +10 -0
  85. data/lib/generators/propel_auth/test/dummy/config/initializers/content_security_policy.rb +25 -0
  86. data/lib/generators/propel_auth/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  87. data/lib/generators/propel_auth/test/dummy/config/initializers/inflections.rb +16 -0
  88. data/lib/generators/propel_auth/test/dummy/config/locales/en.yml +31 -0
  89. data/lib/generators/propel_auth/test/dummy/config/master.key +1 -0
  90. data/lib/generators/propel_auth/test/dummy/config/puma.rb +41 -0
  91. data/lib/generators/propel_auth/test/dummy/config/routes.rb +2 -0
  92. data/lib/generators/propel_auth/test/dummy/config/storage.yml +34 -0
  93. data/lib/generators/propel_auth/test/dummy/config.ru +6 -0
  94. data/lib/generators/propel_auth/test/dummy/db/schema.rb +14 -0
  95. data/lib/generators/propel_auth/test/generators/authentication/controllers/tokens_controller_test.rb +230 -0
  96. data/lib/generators/propel_auth/test/generators/authentication/install_generator_test.rb +490 -0
  97. data/lib/generators/propel_auth/test/generators/authentication/uninstall_generator_test.rb +408 -0
  98. data/lib/generators/propel_auth/test/integration/generator_integration_test.rb +158 -0
  99. data/lib/generators/propel_auth/test/integration/multi_version_generator_test.rb +125 -0
  100. data/lib/generators/propel_auth/unpack_generator.rb +345 -0
  101. data/lib/propel_auth.rb +3 -0
  102. metadata +195 -0
@@ -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