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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +128 -0
- data/README.md +254 -116
- data/lib/generators/{propel_auth → propel_authentication}/install_generator.rb +152 -170
- data/lib/generators/propel_authentication/templates/application_mailer.rb +6 -0
- data/lib/generators/propel_authentication/templates/auth/passwords_controller.rb.tt +132 -0
- data/lib/generators/propel_authentication/templates/auth/signup_controller.rb.tt +242 -0
- data/lib/generators/{propel_auth/templates → propel_authentication/templates/auth}/tokens_controller.rb.tt +39 -22
- data/lib/generators/{propel_auth → propel_authentication}/templates/auth_mailer.rb +3 -1
- data/lib/generators/{propel_auth → propel_authentication}/templates/authenticatable.rb +10 -4
- data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/confirmable.rb +3 -3
- data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/lockable.rb +10 -8
- data/lib/generators/{propel_auth/templates/concerns/propel_authentication.rb → propel_authentication/templates/concerns/propel_authentication_concern.rb} +33 -3
- data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/recoverable.rb +21 -11
- data/lib/generators/propel_authentication/templates/core/configuration_methods.rb +191 -0
- data/lib/generators/propel_authentication/templates/db/seeds.rb +75 -0
- 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_auth/templates/invitation.rb → propel_authentication/templates/models/invitation.rb.tt} +8 -2
- data/lib/generators/propel_authentication/templates/models/organization.rb.tt +12 -0
- data/lib/generators/{propel_auth/templates/user.rb → propel_authentication/templates/models/user.rb.tt} +5 -0
- data/lib/generators/propel_authentication/templates/propel_authentication.rb.tt +218 -0
- data/lib/generators/propel_authentication/templates/routes/auth_routes.rb.tt +55 -0
- data/lib/generators/{propel_auth → propel_authentication}/templates/services/auth_notification_service.rb +3 -3
- data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/confirmable_test.rb.tt +34 -10
- data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/lockable_test.rb.tt +12 -12
- data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/propel_authentication_test.rb.tt +2 -2
- data/lib/generators/{propel_auth → propel_authentication}/templates/test/concerns/recoverable_test.rb.tt +11 -11
- data/lib/generators/{propel_auth → propel_authentication}/templates/test/controllers/auth/lockable_integration_test.rb.tt +18 -15
- data/lib/generators/{propel_auth → 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_auth → propel_authentication}/templates/test/controllers/auth/tokens_controller_test.rb.tt +33 -25
- data/lib/generators/{propel_auth → propel_authentication}/templates/test/mailers/auth_mailer_test.rb.tt +51 -36
- data/lib/generators/{propel_auth → propel_authentication}/templates/user_test.rb.tt +1 -1
- data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/email_confirmation.html.erb +2 -2
- data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/email_confirmation.text.erb +1 -1
- data/lib/generators/{propel_auth → propel_authentication}/test/generators/authentication/install_generator_test.rb +4 -4
- data/lib/generators/{propel_auth → propel_authentication}/test/generators/authentication/uninstall_generator_test.rb +1 -1
- data/lib/generators/{propel_auth → propel_authentication}/test/integration/generator_integration_test.rb +1 -1
- data/lib/generators/{propel_auth → propel_authentication}/test/integration/multi_version_generator_test.rb +13 -12
- data/lib/generators/{propel_auth → propel_authentication}/unpack_generator.rb +55 -38
- data/lib/propel_authentication.rb +3 -0
- metadata +101 -98
- data/lib/generators/propel_auth/core/configuration_methods.rb +0 -134
- data/lib/generators/propel_auth/pack_generator.rb +0 -277
- data/lib/generators/propel_auth/templates/agency.rb +0 -7
- data/lib/generators/propel_auth/templates/agent.rb +0 -7
- data/lib/generators/propel_auth/templates/auth/base_passwords_controller.rb.tt +0 -99
- data/lib/generators/propel_auth/templates/auth/base_tokens_controller.rb.tt +0 -90
- data/lib/generators/propel_auth/templates/auth/passwords_controller.rb.tt +0 -126
- data/lib/generators/propel_auth/templates/db/seeds.rb +0 -29
- data/lib/generators/propel_auth/templates/organization.rb +0 -7
- data/lib/generators/propel_auth/templates/propel_auth.rb.tt +0 -141
- data/lib/propel_auth.rb +0 -3
- /data/lib/generators/{propel_auth → propel_authentication}/templates/concerns/rack_session_disable.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/config/environments/development_email.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_agencies.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_agents.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_invitations.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_organizations.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/db/migrate/create_users.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/test/mailers/previews/auth_mailer_preview.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/account_unlock.html.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/account_unlock.text.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/password_reset.html.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/password_reset.text.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/user_invitation.html.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/templates/views/auth_mailer/user_invitation.text.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/Dockerfile +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/Gemfile +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/README.md +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/Rakefile +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/assets/stylesheets/application.css +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/controllers/application_controller.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/helpers/application_helper.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/jobs/application_job.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/mailers/application_mailer.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/models/application_record.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/layouts/application.html.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/layouts/mailer.html.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/layouts/mailer.text.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/pwa/manifest.json.erb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/app/views/pwa/service-worker.js +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/brakeman +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/dev +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/docker-entrypoint +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/rails +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/rake +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/rubocop +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/setup +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/bin/thrust +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/application.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/boot.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/cable.yml +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/credentials.yml.enc +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/database.yml +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environment.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environments/development.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environments/production.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/environments/test.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/assets.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/content_security_policy.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/filter_parameter_logging.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/initializers/inflections.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/locales/en.yml +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/master.key +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/puma.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/routes.rb +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config/storage.yml +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/config.ru +0 -0
- /data/lib/generators/{propel_auth → propel_authentication}/test/dummy/db/schema.rb +0 -0
- /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
|
-
#
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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
|
-
|
|
37
|
+
determine_configuration
|
|
38
38
|
|
|
39
39
|
if behavior == :revoke
|
|
40
|
-
remove_file "config/initializers/
|
|
41
|
-
say "Removed
|
|
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 "
|
|
45
|
-
say "Created
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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/
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
163
|
+
invoke PropelAuthentication::UnpackGenerator, [], { force: false }
|
|
151
164
|
|
|
152
165
|
say ""
|
|
153
|
-
say "✅ Generator logic extracted to lib/generators/
|
|
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
|
-
|
|
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
|
|
182
|
+
if @auth_namespace.present? || @auth_version.present? || @auth_scope.present?
|
|
170
183
|
say "• Routes: #{auth_route_prefix}/*", :blue
|
|
171
|
-
say "• Controllers: #{controller_namespace}
|
|
184
|
+
say "• Controllers: #{controller_namespace('tokens')}*", :blue
|
|
172
185
|
else
|
|
173
186
|
say "• Routes: /login, /logout, /me", :blue
|
|
174
|
-
say "• Controllers:
|
|
187
|
+
say "• Controllers: TokensController, PasswordsController, etc.", :blue
|
|
175
188
|
end
|
|
176
189
|
|
|
177
190
|
say "\n🎨 Customization:", :bold
|
|
178
|
-
say "• Generator logic: lib/generators/
|
|
179
|
-
say "• Templates: lib/generators/
|
|
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
|
|
195
|
+
say "\n🗑️ To uninstall: rails destroy propel_authentication:install", :yellow
|
|
183
196
|
end
|
|
184
197
|
|
|
185
198
|
private
|
|
186
199
|
|
|
187
|
-
#
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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,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
|