propel_authentication 0.1.3 → 0.2.0

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -0
  3. data/README.md +254 -116
  4. data/lib/generators/{propel_auth → propel_authentication}/install_generator.rb +152 -170
  5. data/lib/generators/propel_authentication/templates/application_mailer.rb +6 -0
  6. data/lib/generators/propel_authentication/templates/auth/passwords_controller.rb.tt +132 -0
  7. data/lib/generators/propel_authentication/templates/auth/signup_controller.rb.tt +242 -0
  8. data/lib/generators/{propel_auth/templates → propel_authentication/templates/auth}/tokens_controller.rb.tt +39 -22
  9. data/lib/generators/{propel_auth → propel_authentication}/templates/auth_mailer.rb +3 -1
  10. data/lib/generators/{propel_auth → propel_authentication}/templates/authenticatable.rb +10 -4
  11. data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/confirmable.rb +3 -3
  12. data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/lockable.rb +10 -8
  13. data/lib/generators/{propel_auth/templates/concerns/propel_authentication.rb → propel_authentication/templates/concerns/propel_authentication_concern.rb} +33 -3
  14. data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/recoverable.rb +21 -11
  15. data/lib/generators/propel_authentication/templates/core/configuration_methods.rb +191 -0
  16. data/lib/generators/propel_authentication/templates/db/seeds.rb +75 -0
  17. data/lib/generators/propel_authentication/templates/doc/signup_flow.md +315 -0
  18. data/lib/generators/propel_authentication/templates/models/agency.rb.tt +13 -0
  19. data/lib/generators/propel_authentication/templates/models/agent.rb.tt +13 -0
  20. data/lib/generators/{propel_auth/templates/invitation.rb → propel_authentication/templates/models/invitation.rb.tt} +8 -2
  21. data/lib/generators/propel_authentication/templates/models/organization.rb.tt +12 -0
  22. data/lib/generators/{propel_auth/templates/user.rb → propel_authentication/templates/models/user.rb.tt} +5 -0
  23. data/lib/generators/propel_authentication/templates/propel_authentication.rb.tt +218 -0
  24. data/lib/generators/propel_authentication/templates/routes/auth_routes.rb.tt +55 -0
  25. data/lib/generators/{propel_auth → propel_authentication}/templates/services/auth_notification_service.rb +3 -3
  26. data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/confirmable_test.rb.tt +34 -10
  27. data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/lockable_test.rb.tt +12 -12
  28. data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/propel_authentication_test.rb.tt +2 -2
  29. data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/recoverable_test.rb.tt +11 -11
  30. data/lib/generators/{propel_auth → propel_authentication}/templates/test/controllers/auth/lockable_integration_test.rb.tt +18 -15
  31. data/lib/generators/{propel_auth → propel_authentication}/templates/test/controllers/auth/password_reset_integration_test.rb.tt +38 -40
  32. data/lib/generators/propel_authentication/templates/test/controllers/auth/signup_controller_test.rb.tt +201 -0
  33. data/lib/generators/{propel_auth → propel_authentication}/templates/test/controllers/auth/tokens_controller_test.rb.tt +33 -25
  34. data/lib/generators/{propel_auth → propel_authentication}/templates/test/mailers/auth_mailer_test.rb.tt +51 -36
  35. data/lib/generators/{propel_auth → propel_authentication}/templates/user_test.rb.tt +1 -1
  36. data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/email_confirmation.html.erb +2 -2
  37. data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/email_confirmation.text.erb +1 -1
  38. data/lib/generators/{propel_auth → propel_authentication}/test/generators/authentication/install_generator_test.rb +4 -4
  39. data/lib/generators/{propel_auth → propel_authentication}/test/generators/authentication/uninstall_generator_test.rb +1 -1
  40. data/lib/generators/{propel_auth → propel_authentication}/test/integration/generator_integration_test.rb +1 -1
  41. data/lib/generators/{propel_auth → propel_authentication}/test/integration/multi_version_generator_test.rb +13 -12
  42. data/lib/generators/{propel_auth → propel_authentication}/unpack_generator.rb +55 -38
  43. data/lib/propel_authentication.rb +3 -0
  44. metadata +101 -98
  45. data/lib/generators/propel_auth/core/configuration_methods.rb +0 -134
  46. data/lib/generators/propel_auth/pack_generator.rb +0 -277
  47. data/lib/generators/propel_auth/templates/agency.rb +0 -7
  48. data/lib/generators/propel_auth/templates/agent.rb +0 -7
  49. data/lib/generators/propel_auth/templates/auth/base_passwords_controller.rb.tt +0 -99
  50. data/lib/generators/propel_auth/templates/auth/base_tokens_controller.rb.tt +0 -90
  51. data/lib/generators/propel_auth/templates/auth/passwords_controller.rb.tt +0 -126
  52. data/lib/generators/propel_auth/templates/db/seeds.rb +0 -29
  53. data/lib/generators/propel_auth/templates/organization.rb +0 -7
  54. data/lib/generators/propel_auth/templates/propel_auth.rb.tt +0 -141
  55. data/lib/propel_auth.rb +0 -3
  56. /data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/rack_session_disable.rb +0 -0
  57. /data/lib/generators/{propel_auth → propel_authentication}/templates/config/environments/development_email.rb +0 -0
  58. /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_agencies.rb +0 -0
  59. /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_agents.rb +0 -0
  60. /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_invitations.rb +0 -0
  61. /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_organizations.rb +0 -0
  62. /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_users.rb +0 -0
  63. /data/lib/generators/{propel_auth → propel_authentication}/templates/test/mailers/previews/auth_mailer_preview.rb +0 -0
  64. /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/account_unlock.html.erb +0 -0
  65. /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/account_unlock.text.erb +0 -0
  66. /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/password_reset.html.erb +0 -0
  67. /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/password_reset.text.erb +0 -0
  68. /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/user_invitation.html.erb +0 -0
  69. /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/user_invitation.text.erb +0 -0
  70. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/Dockerfile +0 -0
  71. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/Gemfile +0 -0
  72. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/README.md +0 -0
  73. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/Rakefile +0 -0
  74. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/assets/stylesheets/application.css +0 -0
  75. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/controllers/application_controller.rb +0 -0
  76. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/helpers/application_helper.rb +0 -0
  77. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/jobs/application_job.rb +0 -0
  78. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/mailers/application_mailer.rb +0 -0
  79. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/models/application_record.rb +0 -0
  80. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/layouts/application.html.erb +0 -0
  81. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/layouts/mailer.html.erb +0 -0
  82. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/layouts/mailer.text.erb +0 -0
  83. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/pwa/manifest.json.erb +0 -0
  84. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/pwa/service-worker.js +0 -0
  85. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/brakeman +0 -0
  86. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/dev +0 -0
  87. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/docker-entrypoint +0 -0
  88. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/rails +0 -0
  89. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/rake +0 -0
  90. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/rubocop +0 -0
  91. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/setup +0 -0
  92. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/thrust +0 -0
  93. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/application.rb +0 -0
  94. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/boot.rb +0 -0
  95. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/cable.yml +0 -0
  96. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/credentials.yml.enc +0 -0
  97. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/database.yml +0 -0
  98. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environment.rb +0 -0
  99. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environments/development.rb +0 -0
  100. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environments/production.rb +0 -0
  101. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environments/test.rb +0 -0
  102. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/assets.rb +0 -0
  103. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/content_security_policy.rb +0 -0
  104. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/filter_parameter_logging.rb +0 -0
  105. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/inflections.rb +0 -0
  106. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/locales/en.yml +0 -0
  107. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/master.key +0 -0
  108. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/puma.rb +0 -0
  109. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/routes.rb +0 -0
  110. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/storage.yml +0 -0
  111. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config.ru +0 -0
  112. /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/db/schema.rb +0 -0
  113. /data/lib/generators/{propel_auth → propel_authentication}/test/generators/authentication/controllers/tokens_controller_test.rb +0 -0
@@ -2,15 +2,15 @@ require 'rails/generators/base'
2
2
  require 'rails/generators/active_record'
3
3
  require_relative 'unpack_generator'
4
4
  ##
5
- # PropelAuth installer that provides JWT-based authentication for Rails applications
5
+ # PropelAuthentication installer that provides JWT-based authentication for Rails applications
6
6
  #
7
7
  # This creates a COMPLETELY STANDALONE authentication system with no gem dependencies.
8
8
  # Usage:
9
- # rails generate propel_auth:install # Full standalone system (runtime + generators)
9
+ # rails generate propel_authentication:install # Full standalone system (runtime + generators)
10
10
  #
11
11
  # This generator:
12
12
  # 1. Installs all runtime code (models, controllers, views, tests, etc.)
13
- # 2. Automatically extracts generator logic to lib/generators/propel_auth/
13
+ # 2. Automatically extracts generator logic to lib/generators/propel_authentication/
14
14
  # 3. Creates a system that requires NO gem dependencies
15
15
  #
16
16
  # After installation, you can remove 'propel_auth' from your Gemfile completely.
@@ -19,13 +19,13 @@ require_relative 'unpack_generator'
19
19
  # Like Rails' built-in generators (action_text:install, devise:install),
20
20
  # this generator is designed to be run once per application.
21
21
  #
22
- require_relative 'core/configuration_methods'
22
+ require_relative 'templates/core/configuration_methods'
23
23
 
24
- module PropelAuth
24
+ module PropelAuthentication
25
25
  class InstallGenerator < Rails::Generators::Base
26
26
  source_root File.expand_path("templates", __dir__)
27
27
  include Rails::Generators::Migration
28
- include PropelAuth::ConfigurationMethods
28
+ include PropelAuthentication::ConfigurationMethods
29
29
 
30
30
  def self.next_migration_number(dir)
31
31
  ActiveRecord::Generators::Base.next_migration_number(dir)
@@ -34,26 +34,27 @@ module PropelAuth
34
34
  desc "Generate JWT-based authentication system with configurable URL architecture"
35
35
 
36
36
  def copy_jwt_initializer
37
- initialize_propel_auth_settings
37
+ determine_configuration
38
38
 
39
39
  if behavior == :revoke
40
- remove_file "config/initializers/propel_auth.rb"
41
- say "Removed PropelAuth configuration", :red
40
+ remove_file "config/initializers/propel_authentication.rb"
41
+ say "Removed PropelAuthentication configuration", :red
42
42
  else
43
43
  # Convert to ERB template to support configuration
44
- template "propel_auth.rb.tt", "config/initializers/propel_auth.rb"
45
- say "Created PropelAuth configuration with namespace: #{namespace_display}, version: #{version_display}", :green
44
+ template "propel_authentication.rb.tt", "config/initializers/propel_authentication.rb"
45
+ say "Created PropelAuthentication configuration with namespace: #{namespace_display}, version: #{version_display}", :green
46
46
  end
47
47
  end
48
48
 
49
-
50
-
51
49
  def copy_authentication_models
52
- copy_file "user.rb", "app/models/user.rb"
53
- copy_file "organization.rb", "app/models/organization.rb"
54
- copy_file "agency.rb", "app/models/agency.rb"
55
- copy_file "agent.rb", "app/models/agent.rb"
56
- copy_file "invitation.rb", "app/models/invitation.rb"
50
+ # Detect rendering engine for conditional facet generation
51
+ @rendering_engine = detect_rendering_engine
52
+
53
+ template "models/user.rb.tt", "app/models/user.rb"
54
+ template "models/organization.rb.tt", "app/models/organization.rb"
55
+ template "models/agency.rb.tt", "app/models/agency.rb"
56
+ template "models/agent.rb.tt", "app/models/agent.rb"
57
+ template "models/invitation.rb.tt", "app/models/invitation.rb"
57
58
  end
58
59
 
59
60
  def copy_authentication_concerns
@@ -61,20 +62,15 @@ module PropelAuth
61
62
  copy_file "concerns/lockable.rb", "app/models/concerns/lockable.rb"
62
63
  copy_file "concerns/recoverable.rb", "app/models/concerns/recoverable.rb"
63
64
  copy_file "concerns/confirmable.rb", "app/models/concerns/confirmable.rb"
64
- copy_file "concerns/propel_authentication.rb", "app/controllers/concerns/propel_authentication.rb"
65
+ copy_file "concerns/propel_authentication_concern.rb", "app/controllers/concerns/propel_authentication_concern.rb"
65
66
  copy_file "concerns/rack_session_disable.rb", "app/controllers/concerns/rack_session_disable.rb"
66
67
  end
67
68
 
68
69
  def copy_jwt_controllers
69
- if auth_namespaced?
70
- template "auth/base_tokens_controller.rb.tt", "app/controllers/#{auth_namespace_path}/auth/base_tokens_controller.rb"
71
- template "auth/base_passwords_controller.rb.tt", "app/controllers/#{auth_namespace_path}/auth/base_passwords_controller.rb"
72
- create_versioned_controller(@auth_version || 'v1', 'tokens', controller_directory)
73
- create_versioned_controller(@auth_version || 'v1', 'passwords', controller_directory)
74
- else
75
- template "tokens_controller.rb.tt", "#{controller_directory}/tokens_controller.rb"
76
- template "auth/passwords_controller.rb.tt", "#{controller_directory}/passwords_controller.rb"
77
- end
70
+ # Generate direct controllers with dynamic file paths (no inheritance)
71
+ template "auth/tokens_controller.rb.tt", controller_file_path('tokens')
72
+ template "auth/passwords_controller.rb.tt", controller_file_path('passwords')
73
+ template "auth/signup_controller.rb.tt", controller_file_path('signup')
78
74
  end
79
75
 
80
76
  def copy_email_infrastructure
@@ -89,9 +85,14 @@ module PropelAuth
89
85
  end
90
86
  end
91
87
 
88
+ def copy_documentation
89
+ copy_file "doc/signup_flow.md", "doc/signup_flow.md"
90
+ end
91
+
92
92
  def copy_authentication_tests
93
93
  template "user_test.rb.tt", "test/models/user_test.rb"
94
94
  template "test/controllers/auth/tokens_controller_test.rb.tt", "test/controllers/auth/tokens_controller_test.rb"
95
+ template "test/controllers/auth/signup_controller_test.rb.tt", "test/controllers/auth/signup_controller_test.rb"
95
96
  template "test/mailers/auth_mailer_test.rb.tt", "test/mailers/auth_mailer_test.rb"
96
97
  copy_file "test/mailers/previews/auth_mailer_preview.rb", "test/mailers/previews/auth_mailer_preview.rb"
97
98
  end
@@ -120,7 +121,19 @@ module PropelAuth
120
121
  end
121
122
 
122
123
  def add_authentication_routes
123
- route generate_routes_content
124
+ routes_file = File.join(destination_root, "config/routes.rb")
125
+ return unless File.exist?(routes_file)
126
+
127
+ routes_content = File.read(routes_file)
128
+
129
+ # Check if authentication routes already exist
130
+ if authentication_routes_exist?(routes_content)
131
+ say "Authentication routes already exist, skipping route generation", :yellow
132
+ return
133
+ end
134
+
135
+ # Use dedicated routes template for clean separation of concerns
136
+ route template_file_for_routes
124
137
  end
125
138
 
126
139
  def copy_authentication_migrations
@@ -136,7 +149,7 @@ module PropelAuth
136
149
  end
137
150
 
138
151
  def extract_generator_for_customization
139
- generator_path = "lib/generators/propel_auth"
152
+ generator_path = "lib/generators/propel_authentication"
140
153
 
141
154
  if File.exist?(generator_path)
142
155
  say ""
@@ -147,13 +160,13 @@ module PropelAuth
147
160
  say "📦 Extracting generator logic for full customization...", :blue
148
161
 
149
162
  # Automatically run the unpack generator to extract generator logic
150
- invoke PropelAuth::UnpackGenerator, [], { force: false }
163
+ invoke PropelAuthentication::UnpackGenerator, [], { force: false }
151
164
 
152
165
  say ""
153
- say "✅ Generator logic extracted to lib/generators/propel_auth/", :green
166
+ say "✅ Generator logic extracted to lib/generators/propel_authentication/", :green
154
167
  say "💡 Your application is now completely standalone - no gem dependency needed!", :cyan
155
168
  say "🗑️ You can now remove 'propel_auth' from your Gemfile", :yellow
156
- say "📦 All PropelAuth runtime code is now in config/initializers/propel_auth.rb", :blue
169
+ say "📦 All PropelAuthentication runtime code is now in config/initializers/propel_authentication.rb", :blue
157
170
  end
158
171
  end
159
172
 
@@ -166,93 +179,52 @@ module PropelAuth
166
179
  say "• Run: rails test", :blue
167
180
  say "• Optional: Remove 'propel_auth' from Gemfile (system is fully extracted)", :cyan
168
181
 
169
- if auth_namespaced?
182
+ if @auth_namespace.present? || @auth_version.present? || @auth_scope.present?
170
183
  say "• Routes: #{auth_route_prefix}/*", :blue
171
- say "• Controllers: #{controller_namespace}::*", :blue
184
+ say "• Controllers: #{controller_namespace('tokens')}*", :blue
172
185
  else
173
186
  say "• Routes: /login, /logout, /me", :blue
174
- say "• Controllers: Auth::*", :blue
187
+ say "• Controllers: TokensController, PasswordsController, etc.", :blue
175
188
  end
176
189
 
177
190
  say "\n🎨 Customization:", :bold
178
- say "• Generator logic: lib/generators/propel_auth/install_generator.rb", :blue
179
- say "• Templates: lib/generators/propel_auth/templates/", :blue
191
+ say "• Generator logic: lib/generators/propel_authentication/install_generator.rb", :blue
192
+ say "• Templates: lib/generators/propel_authentication/templates/", :blue
180
193
  say "• Modify any part of the system - it's all yours now!", :cyan
181
194
 
182
- say "\n🗑️ To uninstall: rails destroy propel_auth:install", :yellow
195
+ say "\n🗑️ To uninstall: rails destroy propel_authentication:install", :yellow
183
196
  end
184
197
 
185
198
  private
186
199
 
187
- # Helper methods for authentication URL structure
188
- def auth_namespaced?
189
- @auth_namespace.present? || @auth_version.present?
190
- end
191
-
192
- def auth_namespace_path
193
- return '' unless auth_namespaced?
194
- path_parts = []
195
- path_parts << @auth_namespace if @auth_namespace.present?
196
- path_parts << @auth_version if @auth_version.present?
197
- path_parts.join('/')
198
- end
199
-
200
- def controller_namespace
201
- auth_controller_namespace
202
- end
203
-
204
- def controller_directory
205
- auth_controller_directory
206
- end
207
-
208
- def api_only_app?
209
- # Check if this is a Rails API-only application
210
- Rails.application.config.respond_to?(:api_only) && Rails.application.config.api_only
211
- end
212
-
213
- # Path helper methods for tests
214
- def login_path_helper
215
- if auth_namespaced?
216
- "'#{auth_route_prefix}/login'"
200
+ # Check if authentication routes already exist in routes file
201
+ def authentication_routes_exist?(routes_content)
202
+ # Check for key authentication routes based on our configuration
203
+ if @auth_namespace.present? && @auth_version.present? && @auth_scope.present?
204
+ # Check for api/v1/auth/login pattern
205
+ routes_content.match?(/namespace\s+:#{@auth_namespace}\s+do.*?namespace\s+:#{@auth_version}\s+do.*?namespace\s+:#{@auth_scope}\s+do.*?post\s+['"]login['"],?\s+to:\s+['"]tokens#create['"], as: :login/m)
206
+ elsif @auth_namespace.present? && @auth_version.present?
207
+ # Check for api/v1/login pattern
208
+ routes_content.match?(/namespace\s+:#{@auth_namespace}\s+do.*?namespace\s+:#{@auth_version}\s+do.*?post\s+['"]login['"],?\s+to:\s+['"]tokens#create['"], as: :login/m)
209
+ elsif @auth_namespace.present? && @auth_scope.present?
210
+ # Check for api/auth/login pattern
211
+ routes_content.match?(/namespace\s+:#{@auth_namespace}\s+do.*?namespace\s+:#{@auth_scope}\s+do.*?post\s+['"]login['"],?\s+to:\s+['"]tokens#create['"], as: :login/m)
212
+ elsif @auth_scope.present?
213
+ # Check for auth/login pattern
214
+ routes_content.match?(/namespace\s+:#{@auth_scope}\s+do.*?post\s+['"]login['"],?\s+to:\s+['"]tokens#create['"], as: :login/m)
215
+ elsif @auth_namespace.present?
216
+ # Check for api/login pattern
217
+ routes_content.match?(/namespace\s+:#{@auth_namespace}\s+do.*?post\s+['"]login['"],?\s+to:\s+['"]tokens#create['"], as: :login/m)
217
218
  else
218
- "'/login'"
219
+ # Check for root-level login route
220
+ routes_content.match?(/post\s+['"]login['"],?\s+to:\s+['"]tokens#create['"], as: :login/)
219
221
  end
220
222
  end
221
223
 
222
- def me_path_helper
223
- if auth_namespaced?
224
- "'#{auth_route_prefix}/me'"
225
- else
226
- "'/me'"
227
- end
228
- end
229
-
230
- def logout_path_helper
231
- if auth_namespaced?
232
- "'#{auth_route_prefix}/logout'"
233
- else
234
- "'/logout'"
235
- end
236
- end
237
-
238
- def unlock_path_helper
239
- if auth_namespaced?
240
- "'#{auth_route_prefix}/unlock'"
241
- else
242
- "'/unlock'"
243
- end
244
- end
245
-
246
- def password_reset_path_helper
247
- if auth_namespaced?
248
- "'#{auth_route_prefix}/reset'"
249
- else
250
- "'/reset'"
251
- end
252
- end
253
-
254
- def reset_path_helper
255
- password_reset_path_helper
224
+ # Render authentication routes template to string
225
+ def template_file_for_routes
226
+ source_path = find_in_source_paths("routes/auth_routes.rb.tt")
227
+ ERB.new(File.read(source_path), trim_mode: '-').result(binding)
256
228
  end
257
229
 
258
230
  def add_gem_if_missing(gem_name, version_requirement = nil, options = {})
@@ -284,71 +256,7 @@ module PropelAuth
284
256
  def migration_exists?(migration_name)
285
257
  Dir.glob("#{destination_root}/db/migrate/*_#{migration_name}.rb").any?
286
258
  end
287
-
288
- def generate_routes_content
289
- if auth_namespaced?
290
- route_lines = ["# JWT Authentication routes for #{auth_route_prefix}"]
291
-
292
- # Build namespace opening
293
- namespace_parts = []
294
- namespace_parts << @auth_namespace if @auth_namespace.present?
295
- namespace_parts << @auth_version if @auth_version.present?
296
-
297
- namespace_parts.each_with_index do |part, index|
298
- indent = " " * index
299
- route_lines << "#{indent}namespace :#{part} do"
300
- end
301
-
302
- # Add authentication routes directly in namespace (no nested /auth/)
303
- route_indent = " " * namespace_parts.length
304
- route_lines << "#{route_indent}post 'login', to: 'auth/tokens#create'"
305
- route_lines << "#{route_indent}get 'me', to: 'auth/tokens#me'"
306
- route_lines << "#{route_indent}delete 'logout', to: 'auth/tokens#destroy'"
307
- route_lines << "#{route_indent}post 'unlock', to: 'auth/tokens#unlock'"
308
- route_lines << "#{route_indent}post 'reset', to: 'auth/passwords#create'"
309
- route_lines << "#{route_indent}get 'reset', to: 'auth/passwords#show'"
310
- route_lines << "#{route_indent}patch 'reset', to: 'auth/passwords#update'"
311
-
312
- # Build namespace closing
313
- namespace_parts.length.times do |index|
314
- indent = " " * (namespace_parts.length - 1 - index)
315
- route_lines << "#{indent}end"
316
- end
317
-
318
- route_lines.join("\n")
319
- else
320
- <<~ROUTES
321
- # JWT Authentication routes
322
- post 'login', to: 'auth/tokens#create'
323
- get 'me', to: 'auth/tokens#me'
324
- delete 'logout', to: 'auth/tokens#destroy'
325
- post 'unlock', to: 'auth/tokens#unlock'
326
- post 'reset', to: 'auth/passwords#create'
327
- get 'reset', to: 'auth/passwords#show'
328
- patch 'reset', to: 'auth/passwords#update'
329
- ROUTES
330
- end
331
- end
332
-
333
- def create_versioned_controller(version, controller_type, directory)
334
- case controller_type
335
- when 'tokens'
336
- controller_content = <<~RUBY
337
- # Generated versioned controller for API #{version}
338
- class Api::#{version.camelize}::Auth::TokensController < Api::Auth::BaseTokensController
339
- end
340
- RUBY
341
- when 'passwords'
342
- controller_content = <<~RUBY
343
- # Generated versioned controller for API #{version}
344
- class Api::#{version.camelize}::Auth::PasswordsController < Api::Auth::BasePasswordsController
345
- end
346
- RUBY
347
- end
348
-
349
- create_file "#{directory}/#{controller_type}_controller.rb", controller_content
350
- end
351
-
259
+
352
260
  # Fixture content methods
353
261
  def organizations_fixture
354
262
  <<~FIXTURE
@@ -451,17 +359,70 @@ module PropelAuth
451
359
  organization: acme_org
452
360
  created_at: <%= 10.days.ago %>
453
361
  updated_at: <%= 2.days.ago %>
362
+
363
+ sales_agency:
364
+ name: "Sales Department"
365
+ organization: acme_org
366
+ created_at: <%= 9.days.ago %>
367
+ updated_at: <%= 2.days.ago %>
368
+
369
+ tech_agency:
370
+ name: "Tech Solutions"
371
+ organization: tech_startup
372
+ created_at: <%= 8.days.ago %>
373
+ updated_at: <%= 1.day.ago %>
374
+
375
+ support_agency:
376
+ name: "Support Team"
377
+ organization: tech_startup
378
+ created_at: <%= 7.days.ago %>
379
+ updated_at: <%= 1.day.ago %>
454
380
  FIXTURE
455
381
  end
456
382
 
457
383
  def agents_fixture
458
384
  <<~FIXTURE
459
- marketing_agent:
385
+ john_marketing_agent:
460
386
  user: john_user
461
387
  agency: marketing_agency
462
388
  role: "manager"
463
389
  created_at: <%= 8.days.ago %>
464
390
  updated_at: <%= 1.day.ago %>
391
+
392
+ confirmed_sales_agent:
393
+ user: confirmed_user
394
+ agency: sales_agency
395
+ role: "analyst"
396
+ created_at: <%= 7.days.ago %>
397
+ updated_at: <%= 1.day.ago %>
398
+
399
+ locked_marketing_agent:
400
+ user: locked_user
401
+ agency: marketing_agency
402
+ role: "coordinator"
403
+ created_at: <%= 6.days.ago %>
404
+ updated_at: <%= 1.day.ago %>
405
+
406
+ jane_tech_agent:
407
+ user: jane_user
408
+ agency: tech_agency
409
+ role: "developer"
410
+ created_at: <%= 5.days.ago %>
411
+ updated_at: <%= 1.day.ago %>
412
+
413
+ expired_support_agent:
414
+ user: expired_confirmation_user
415
+ agency: support_agency
416
+ role: "specialist"
417
+ created_at: <%= 4.days.ago %>
418
+ updated_at: <%= 1.day.ago %>
419
+
420
+ locked_sales_agent:
421
+ user: locked_user
422
+ agency: sales_agency
423
+ role: "trainee"
424
+ created_at: <%= 3.days.ago %>
425
+ updated_at: <%= 1.day.ago %>
465
426
  FIXTURE
466
427
  end
467
428
 
@@ -478,5 +439,26 @@ module PropelAuth
478
439
  updated_at: <%= 3.days.ago %>
479
440
  FIXTURE
480
441
  end
442
+
443
+ def detect_rendering_engine
444
+ # Check for common rendering engines in order of preference
445
+ propel_facets_path = File.join(destination_root, 'config/initializers/propel_facets.rb')
446
+ return 'json_facet' if File.exist?(propel_facets_path)
447
+ return 'graphiti' if defined?(Graphiti) || gem_present?('graphiti')
448
+ return 'blueprinter' if defined?(Blueprinter) || gem_present?('blueprinter')
449
+
450
+ # Default to json_facet if no other engine detected
451
+ 'json_facet'
452
+ end
453
+
454
+ def gem_present?(gem_name)
455
+ # Check if gem is in Gemfile
456
+ gemfile_path = File.join(destination_root, 'Gemfile')
457
+ return false unless File.exist?(gemfile_path)
458
+
459
+ File.read(gemfile_path).match?(/gem\s+['"]#{gem_name}['"]/)
460
+ rescue
461
+ false
462
+ end
481
463
  end
482
464
  end
@@ -0,0 +1,6 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: "from@example.com"
3
+ layout "mailer"
4
+ end
5
+
6
+
@@ -0,0 +1,132 @@
1
+ class <%= auth_controller_class_name('passwords') %> < ApplicationController
2
+ <%- unless api_only_app? -%>
3
+ include RackSessionDisable
4
+ <%- end -%>
5
+ include PropelAuthenticationConcern
6
+
7
+ # POST <%= auth_route_prefix %>/reset
8
+ def create
9
+ email_address = params[:email_address]
10
+
11
+ # Validate email_address parameter
12
+ if email_address.blank?
13
+ return render json: { error: "Email address is required" }, status: :unprocessable_entity
14
+ end
15
+
16
+ # Validate email format
17
+ unless email_address.match?(URI::MailTo::EMAIL_REGEXP)
18
+ return render json: { error: "Email address format is invalid" }, status: :unprocessable_entity
19
+ end
20
+
21
+ user = User.find_by(email_address: email_address)
22
+
23
+ if user
24
+ # Generate and save reset token
25
+ user.generate_password_reset_token
26
+
27
+ # Send password reset email
28
+ if PropelAuthentication.configuration.enable_email_notifications
29
+ email_result = AuthNotificationService.send_password_reset_email(user)
30
+
31
+ if email_result[:success]
32
+ Rails.logger.info "Password reset email sent successfully to #{user.email_address}"
33
+ else
34
+ Rails.logger.error "Failed to send password reset email to #{user.email_address}: #{email_result[:error]}"
35
+ end
36
+ end
37
+
38
+ render json: {
39
+ message: "Password reset instructions have been sent to your email address"
40
+ }, status: :ok
41
+ else
42
+ # Don't reveal whether the email exists for security
43
+ render json: {
44
+ message: "Password reset instructions have been sent to your email address"
45
+ }, status: :ok
46
+ end
47
+ rescue => e
48
+ Rails.logger.error "Password reset creation error: #{e.message}"
49
+ render json: { error: "Unable to process password reset request" }, status: :unprocessable_entity
50
+ end
51
+
52
+ # GET <%= auth_route_prefix %>/reset
53
+ def show
54
+ token = params[:token]
55
+
56
+ if token.blank?
57
+ render json: { error: 'Reset token is required' }, status: :unprocessable_entity
58
+ return
59
+ end
60
+
61
+ user = User.find_by_jwt_password_reset_token(token)
62
+
63
+ if user
64
+ render json: {
65
+ valid: true, # Test expects this field
66
+ message: 'Valid reset token',
67
+ user: {
68
+ id: user.id,
69
+ email_address: user.email_address,
70
+ token: token
71
+ }
72
+ }, status: :ok
73
+ else
74
+ render json: {
75
+ valid: false, # Test expects this field
76
+ error: 'Invalid or expired reset token'
77
+ }, status: :unauthorized
78
+ end
79
+ rescue => e
80
+ Rails.logger.error "Password reset show error: #{e.message}"
81
+ render json: { error: 'Invalid reset token' }, status: :unauthorized
82
+ end
83
+
84
+ # PATCH <%= auth_route_prefix %>/reset
85
+ def update
86
+ token = params[:token]
87
+ new_password = params[:password]
88
+ password_confirmation = params[:password_confirmation]
89
+
90
+ # Validate required parameters
91
+ if token.blank?
92
+ return render json: { error: 'Reset token is required' }, status: :unprocessable_entity
93
+ end
94
+
95
+ if new_password.blank?
96
+ return render json: { error: 'New password is required' }, status: :unprocessable_entity
97
+ end
98
+
99
+ if new_password != password_confirmation
100
+ return render json: { error: 'Password confirmation does not match password' }, status: :unprocessable_entity
101
+ end
102
+
103
+ user = User.find_by_jwt_password_reset_token(token)
104
+
105
+ if user.nil?
106
+ return render json: { error: 'Invalid or expired reset token' }, status: :unauthorized
107
+ end
108
+
109
+ # Update password and clear reset token
110
+ if user.reset_password_with_token!(token, new_password)
111
+ render json: {
112
+ message: 'Password has been reset successfully',
113
+ user: {
114
+ id: user.id,
115
+ email_address: user.email_address
116
+ }
117
+ }, status: :ok
118
+ else
119
+ # Return specific validation errors from the user model
120
+ error_messages = user.errors.full_messages
121
+ primary_error = error_messages.first || 'Password validation failed'
122
+
123
+ render json: {
124
+ error: primary_error, # Use specific validation message
125
+ details: error_messages
126
+ }, status: :unprocessable_entity
127
+ end
128
+ rescue => e
129
+ Rails.logger.error "Password reset update error: #{e.message}"
130
+ render json: { error: 'Unable to reset password' }, status: :unprocessable_entity
131
+ end
132
+ end