propel_authentication 0.3.0 → 0.3.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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -0
  3. data/lib/generators/propel_authentication/install_generator.rb +188 -240
  4. data/lib/generators/propel_authentication/templates/auth/passwords_controller.rb.tt +1 -4
  5. data/lib/generators/propel_authentication/templates/auth/signup_controller.rb.tt +3 -6
  6. data/lib/generators/propel_authentication/templates/auth/tokens_controller.rb.tt +2 -5
  7. data/lib/generators/propel_authentication/templates/core/configuration_methods.rb +38 -1
  8. data/lib/generators/propel_authentication/templates/db/migrate/create_agencies.rb +2 -0
  9. data/lib/generators/propel_authentication/templates/db/migrate/create_agents.rb +6 -1
  10. data/lib/generators/propel_authentication/templates/db/migrate/create_users.rb +2 -0
  11. data/lib/generators/propel_authentication/templates/db/propel_seeds.rb.tt +145 -0
  12. data/lib/generators/propel_authentication/templates/doc/signup_flow.md +9 -4
  13. data/lib/generators/propel_authentication/templates/fixtures/agencies.yml.erb +23 -0
  14. data/lib/generators/propel_authentication/templates/fixtures/agents.yml.erb +71 -0
  15. data/lib/generators/propel_authentication/templates/fixtures/invitations.yml.erb +9 -0
  16. data/lib/generators/propel_authentication/templates/fixtures/organizations.yml.erb +21 -0
  17. data/lib/generators/propel_authentication/templates/fixtures/users.yml.erb +77 -0
  18. data/lib/generators/propel_authentication/templates/models/agency.rb.tt +4 -0
  19. data/lib/generators/propel_authentication/templates/models/agent.rb.tt +9 -0
  20. data/lib/generators/propel_authentication/templates/models/invitation.rb.tt +3 -1
  21. data/lib/generators/propel_authentication/templates/models/organization.rb.tt +3 -0
  22. data/lib/generators/propel_authentication/templates/propel_authentication.rb.tt +33 -6
  23. data/lib/generators/propel_authentication/templates/test/controllers/auth/lockable_integration_test.rb.tt +1 -1
  24. data/lib/generators/propel_authentication/templates/test/controllers/auth/password_reset_integration_test.rb.tt +1 -1
  25. data/lib/generators/propel_authentication/templates/test/controllers/auth/tokens_controller_test.rb.tt +4 -4
  26. data/lib/propel_authentication.rb +1 -1
  27. metadata +8 -3
  28. data/lib/generators/propel_authentication/templates/db/seeds.rb +0 -75
@@ -1,8 +1,13 @@
1
1
  class CreateAgents < ActiveRecord::Migration[8.0]
2
2
  def change
3
3
  create_table :agents do |t|
4
- t.references :user, null: false, foreign_key: true
4
+ <% if PropelAuthentication.configuration.organization_required? %>
5
+ t.references :organization, null: false, foreign_key: true
6
+ <% end %>
7
+ <% if PropelAuthentication.configuration.agency_required? %>
5
8
  t.references :agency, null: false, foreign_key: true
9
+ <% end %>
10
+ t.references :user, null: false, foreign_key: true
6
11
  t.string :role, null: false, default: 'agent'
7
12
  t.string :title
8
13
  t.timestamps
@@ -23,7 +23,9 @@ class CreateUsers < ActiveRecord::Migration[8.0]
23
23
  t.datetime :locked_at
24
24
 
25
25
  # Multi-tenant association
26
+ <% if PropelAuthentication.configuration.organization_required? %>
26
27
  t.references :organization, null: false, foreign_key: true
28
+ <% end %>
27
29
 
28
30
  if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?('postgresql')
29
31
  t.jsonb :metadata, default: {}
@@ -0,0 +1,145 @@
1
+ # This file should ensure the existence of records required to run the application in every environment (production,
2
+ # development, test). The code here should be idempotent so that it can be executed at any point in every environment.
3
+ # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
4
+ #
5
+ # Example:
6
+ #
7
+ # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
8
+ # MovieGenre.find_or_create_by!(name: genre_name)
9
+ # end
10
+
11
+ # Create comprehensive tenancy structure for authentication testing
12
+ puts "🏢 Creating test organization structure..."
13
+
14
+ organization = Organization.find_or_create_by!(name: 'Test Organization') do |org|
15
+ org.website = 'https://test.example.com'
16
+ org.time_zone = 'UTC'
17
+ end
18
+
19
+ # Create agencies for proper tenancy structure
20
+ <% if PropelAuthentication.configuration.agency_required? -%>
21
+ <% if PropelAuthentication.configuration.organization_required? -%>
22
+ # Full tenancy mode: agencies belong to organization
23
+ main_agency = Agency.find_or_create_by!(name: 'Main Department', organization: organization) do |agency|
24
+ agency.phone_number = '+1-555-0101'
25
+ agency.address = '123 Main Street'
26
+ end
27
+
28
+ support_agency = Agency.find_or_create_by!(name: 'Support Department', organization: organization) do |agency|
29
+ agency.phone_number = '+1-555-0102'
30
+ agency.address = '123 Support Street'
31
+ end
32
+ <% else -%>
33
+ # Simple mode: agencies are standalone
34
+ main_agency = Agency.find_or_create_by!(name: 'Main Department') do |agency|
35
+ agency.phone_number = '+1-555-0101'
36
+ agency.address = '123 Main Street'
37
+ end
38
+
39
+ support_agency = Agency.find_or_create_by!(name: 'Support Department') do |agency|
40
+ agency.phone_number = '+1-555-0102'
41
+ agency.address = '123 Support Street'
42
+ end
43
+ <% end -%>
44
+ <% end -%>
45
+
46
+ # Create test users with proper confirmations
47
+ <% if PropelAuthentication.configuration.organization_required? -%>
48
+ # Full tenancy mode: users belong to organization
49
+ user = User.find_or_create_by!(email_address: 'test@example.com') do |u|
50
+ u.username = 'testuser'
51
+ u.email_address = 'test@example.com'
52
+ u.password = 'password123'
53
+ u.password_confirmation = 'password123'
54
+ u.organization = organization
55
+ u.first_name = 'Test'
56
+ u.last_name = 'User'
57
+ u.confirmed_at = 1.week.ago # Confirmed user for easier testing
58
+ end
59
+
60
+ admin_user = User.find_or_create_by!(email_address: 'admin@example.com') do |u|
61
+ u.username = 'adminuser'
62
+ u.email_address = 'admin@example.com'
63
+ u.password = 'password123'
64
+ u.password_confirmation = 'password123'
65
+ u.organization = organization
66
+ u.first_name = 'Admin'
67
+ u.last_name = 'User'
68
+ u.confirmed_at = 1.week.ago # Confirmed admin for easier testing
69
+ end
70
+ <% else -%>
71
+ # Simple mode: users are standalone
72
+ user = User.find_or_create_by!(email_address: 'test@example.com') do |u|
73
+ u.username = 'testuser'
74
+ u.email_address = 'test@example.com'
75
+ u.password = 'password123'
76
+ u.password_confirmation = 'password123'
77
+ u.first_name = 'Test'
78
+ u.last_name = 'User'
79
+ u.confirmed_at = 1.week.ago # Confirmed user for easier testing
80
+ end
81
+
82
+ admin_user = User.find_or_create_by!(email_address: 'admin@example.com') do |u|
83
+ u.username = 'adminuser'
84
+ u.email_address = 'admin@example.com'
85
+ u.password = 'password123'
86
+ u.password_confirmation = 'password123'
87
+ u.first_name = 'Admin'
88
+ u.last_name = 'User'
89
+ u.confirmed_at = 1.week.ago # Confirmed admin for easier testing
90
+ end
91
+ <% end -%>
92
+
93
+ # Create agent associations for agency access (CRITICAL for tenancy validation)
94
+ <% if PropelAuthentication.configuration.agency_required? -%>
95
+ <% if PropelAuthentication.configuration.organization_required? -%>
96
+ # Full tenancy mode: agents belong to both organization and agency
97
+ Agent.find_or_create_by!(user: user, agency: main_agency, organization: organization) do |agent|
98
+ agent.role = 'member'
99
+ end
100
+
101
+ Agent.find_or_create_by!(user: admin_user, agency: main_agency, organization: organization) do |agent|
102
+ agent.role = 'manager'
103
+ end
104
+
105
+ Agent.find_or_create_by!(user: admin_user, agency: support_agency, organization: organization) do |agent|
106
+ agent.role = 'admin'
107
+ end
108
+ <% else -%>
109
+ # Simple mode: agents still need organization even if users don't require it
110
+ Agent.find_or_create_by!(user: user, agency: main_agency, organization: organization) do |agent|
111
+ agent.role = 'member'
112
+ end
113
+
114
+ Agent.find_or_create_by!(user: admin_user, agency: main_agency, organization: organization) do |agent|
115
+ agent.role = 'manager'
116
+ end
117
+
118
+ Agent.find_or_create_by!(user: admin_user, agency: support_agency, organization: organization) do |agent|
119
+ agent.role = 'admin'
120
+ end
121
+ <% end -%>
122
+ <% end -%>
123
+
124
+ <% if PropelAuthentication.configuration.organization_required? -%>
125
+ puts "✅ Created test organization: #{organization.name}"
126
+ <% if PropelAuthentication.configuration.agency_required? -%>
127
+ puts "✅ Created agencies: #{Agency.where(organization: organization).pluck(:name).join(', ')}"
128
+ <% end -%>
129
+ puts "✅ Created test user: #{user.email_address} (password: password123) - Organization: #{user.organization.name}"
130
+ puts "✅ Created admin user: #{admin_user.email_address} (password: password123) - Organization: #{admin_user.organization.name}"
131
+ puts "✅ Created agent associations for proper tenancy access"
132
+ puts ""
133
+ puts "🎯 Test user agency access: #{user.agency_ids}"
134
+ puts "🎯 Admin user agency access: #{admin_user.agency_ids}"
135
+ <% else -%>
136
+ puts "✅ Created agencies: #{Agency.pluck(:name).join(', ')}"
137
+ puts "✅ Created test user: #{user.email_address} (password: password123)"
138
+ puts "✅ Created admin user: #{admin_user.email_address} (password: password123)"
139
+ puts "✅ Created agent associations for agency access"
140
+ puts ""
141
+ puts "🎯 Test user agency access: #{user.agency_ids}"
142
+ puts "🎯 Admin user agency access: #{admin_user.agency_ids}"
143
+ <% end -%>
144
+ puts ""
145
+ puts "🚀 You can now test login with POST /api/v1/login"
@@ -12,12 +12,17 @@ The signup flow creates:
12
12
 
13
13
  ## Configuration
14
14
 
15
- Agency tenancy can be enabled/disabled in `config/initializers/propel_api.rb`:
15
+ Multi-tenancy settings can be configured in `config/initializers/propel_authentication.rb`:
16
16
 
17
17
  ```ruby
18
- PropelApi.configure do |config|
19
- config.agency_tenancy = true # Enable agency-level tenancy
20
- # config.agency_tenancy = false # Disable for organization-only tenancy
18
+ PropelAuthentication.configure do |config|
19
+ # Multi-tenancy master switch
20
+ config.tenancy_enabled = true # Enable/disable entire tenancy system
21
+
22
+ # Individual tenancy model requirements
23
+ config.organization_required = true # Users must belong to organizations
24
+ config.agency_required = true # Enable agency-level tenancy within orgs
25
+ # config.agency_required = false # Disable for organization-only tenancy
21
26
  end
22
27
  ```
23
28
 
@@ -0,0 +1,23 @@
1
+ marketing_agency:
2
+ name: "Marketing Solutions"
3
+ organization: acme_org
4
+ created_at: <%= 10.days.ago %>
5
+ updated_at: <%= 2.days.ago %>
6
+
7
+ sales_agency:
8
+ name: "Sales Department"
9
+ organization: acme_org
10
+ created_at: <%= 9.days.ago %>
11
+ updated_at: <%= 2.days.ago %>
12
+
13
+ tech_agency:
14
+ name: "Tech Solutions"
15
+ organization: tech_startup
16
+ created_at: <%= 8.days.ago %>
17
+ updated_at: <%= 1.day.ago %>
18
+
19
+ support_agency:
20
+ name: "Support Team"
21
+ organization: tech_startup
22
+ created_at: <%= 7.days.ago %>
23
+ updated_at: <%= 1.day.ago %>
@@ -0,0 +1,71 @@
1
+ john_marketing_agent:
2
+ user: john_user
3
+ <% if PropelAuthentication.configuration.organization_required? -%>
4
+ organization: acme_org
5
+ <% end -%>
6
+ <% if PropelAuthentication.configuration.agency_required? -%>
7
+ agency: marketing_agency
8
+ <% end -%>
9
+ role: "manager"
10
+ created_at: <%= 8.days.ago %>
11
+ updated_at: <%= 1.day.ago %>
12
+
13
+ confirmed_sales_agent:
14
+ user: confirmed_user
15
+ <% if PropelAuthentication.configuration.organization_required? -%>
16
+ organization: acme_org
17
+ <% end -%>
18
+ <% if PropelAuthentication.configuration.agency_required? -%>
19
+ agency: sales_agency
20
+ <% end -%>
21
+ role: "analyst"
22
+ created_at: <%= 7.days.ago %>
23
+ updated_at: <%= 1.day.ago %>
24
+
25
+ locked_marketing_agent:
26
+ user: locked_user
27
+ <% if PropelAuthentication.configuration.organization_required? -%>
28
+ organization: acme_org
29
+ <% end -%>
30
+ <% if PropelAuthentication.configuration.agency_required? -%>
31
+ agency: marketing_agency
32
+ <% end -%>
33
+ role: "coordinator"
34
+ created_at: <%= 6.days.ago %>
35
+ updated_at: <%= 1.day.ago %>
36
+
37
+ jane_tech_agent:
38
+ user: jane_user
39
+ <% if PropelAuthentication.configuration.organization_required? -%>
40
+ organization: tech_startup
41
+ <% end -%>
42
+ <% if PropelAuthentication.configuration.agency_required? -%>
43
+ agency: tech_agency
44
+ <% end -%>
45
+ role: "developer"
46
+ created_at: <%= 5.days.ago %>
47
+ updated_at: <%= 1.day.ago %>
48
+
49
+ expired_support_agent:
50
+ user: expired_confirmation_user
51
+ <% if PropelAuthentication.configuration.organization_required? -%>
52
+ organization: tech_startup
53
+ <% end -%>
54
+ <% if PropelAuthentication.configuration.agency_required? -%>
55
+ agency: support_agency
56
+ <% end -%>
57
+ role: "specialist"
58
+ created_at: <%= 4.days.ago %>
59
+ updated_at: <%= 1.day.ago %>
60
+
61
+ locked_sales_agent:
62
+ user: locked_user
63
+ <% if PropelAuthentication.configuration.organization_required? -%>
64
+ organization: acme_org
65
+ <% end -%>
66
+ <% if PropelAuthentication.configuration.agency_required? -%>
67
+ agency: sales_agency
68
+ <% end -%>
69
+ role: "trainee"
70
+ created_at: <%= 3.days.ago %>
71
+ updated_at: <%= 1.day.ago %>
@@ -0,0 +1,9 @@
1
+ pending_invitation:
2
+ email_address: "newuser@example.com"
3
+ first_name: "New"
4
+ last_name: "User"
5
+ organization: acme_org
6
+ inviter: john_user
7
+ status: "pending"
8
+ created_at: <%= 3.days.ago %>
9
+ updated_at: <%= 3.days.ago %>
@@ -0,0 +1,21 @@
1
+ acme_org:
2
+ name: "Acme Corporation"
3
+ website: "https://acme-corp.com"
4
+ time_zone: "UTC"
5
+ logo: "https://acme-corp.com/logo.png"
6
+ slug: "acme-corporation"
7
+ metadata: { org_type: "enterprise", industry: "technology", size: "large" }
8
+ settings: { theme: "corporate", timezone: "EST", features: "premium" }
9
+ created_at: <%= 30.days.ago %>
10
+ updated_at: <%= 1.day.ago %>
11
+
12
+ tech_startup:
13
+ name: "Tech Startup Inc"
14
+ website: "https://techstartup.io"
15
+ time_zone: "America/New_York"
16
+ logo: "https://techstartup.io/logo.png"
17
+ slug: "tech-startup-inc"
18
+ metadata: { org_type: "startup", industry: "software", size: "small" }
19
+ settings: { theme: "modern", timezone: "PST", features: "basic" }
20
+ created_at: <%= 15.days.ago %>
21
+ updated_at: <%= 2.days.ago %>
@@ -0,0 +1,77 @@
1
+ john_user:
2
+ email_address: "john@example.com"
3
+ username: "john_doe"
4
+ first_name: "John"
5
+ last_name: "Doe"
6
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
7
+ organization: acme_org
8
+ status: "active"
9
+ confirmed_at: null
10
+ confirmation_token: "<%= SecureRandom.hex(32) %>"
11
+ confirmation_sent_at: <%= 1.hour.ago %>
12
+ metadata: { user_type: "admin", department: "engineering", priority: "high" }
13
+ settings: { theme: "dark", language: "en", notifications: true }
14
+ created_at: <%= 7.days.ago %>
15
+ updated_at: <%= 1.day.ago %>
16
+
17
+ jane_user:
18
+ email_address: "jane@example.com"
19
+ username: "jane_smith"
20
+ first_name: "Jane"
21
+ last_name: "Smith"
22
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
23
+ organization: tech_startup
24
+ status: "active"
25
+ confirmed_at: null
26
+ failed_login_attempts: 0
27
+ locked_at: null
28
+ metadata: { user_type: "manager", department: "marketing", priority: "high" }
29
+ settings: { theme: "light", language: "en", notifications: false }
30
+ created_at: <%= 5.days.ago %>
31
+ updated_at: <%= 1.day.ago %>
32
+
33
+ confirmed_user:
34
+ email_address: "confirmed@example.com"
35
+ username: "confirmed_user"
36
+ first_name: "Confirmed"
37
+ last_name: "User"
38
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
39
+ organization: acme_org
40
+ status: "active"
41
+ confirmed_at: <%= 7.days.ago %>
42
+ metadata: { user_type: "employee", department: "sales", priority: "medium" }
43
+ settings: { theme: "auto", language: "es", notifications: true }
44
+ created_at: <%= 14.days.ago %>
45
+ updated_at: <%= 1.day.ago %>
46
+
47
+ locked_user:
48
+ email_address: "locked@example.com"
49
+ username: "locked_user"
50
+ first_name: "Locked"
51
+ last_name: "User"
52
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
53
+ organization: acme_org
54
+ status: "active"
55
+ confirmed_at: <%= 10.days.ago %>
56
+ failed_login_attempts: 5
57
+ locked_at: <%= 1.hour.ago %>
58
+ metadata: { user_type: "employee", department: "support", priority: "low" }
59
+ settings: { theme: "light", language: "en", notifications: false }
60
+ created_at: <%= 20.days.ago %>
61
+ updated_at: <%= 1.hour.ago %>
62
+
63
+ expired_confirmation_user:
64
+ email_address: "expired@example.com"
65
+ username: "expired_user"
66
+ first_name: "Expired"
67
+ last_name: "User"
68
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
69
+ organization: tech_startup
70
+ status: "active"
71
+ confirmed_at: null
72
+ confirmation_token: "<%= SecureRandom.hex(32) %>"
73
+ confirmation_sent_at: <%= 25.hours.ago %>
74
+ metadata: { user_type: "test", department: "qa", priority: "low" }
75
+ settings: { theme: "dark", language: "fr", notifications: false }
76
+ created_at: <%= 30.days.ago %>
77
+ updated_at: <%= 25.hours.ago %>
@@ -1,7 +1,11 @@
1
1
  class Agency < ApplicationRecord
2
2
  # Multi-tenant associations
3
+ <% if PropelAuthentication.configuration.organization_required? -%>
3
4
  belongs_to :organization
5
+ <% end -%>
6
+ <% if PropelAuthentication.configuration.agency_required? -%>
4
7
  has_many :agents, dependent: :destroy
8
+ <% end -%>
5
9
 
6
10
  validates :name, presence: true
7
11
  <% if @rendering_engine == 'json_facet' -%>
@@ -1,9 +1,18 @@
1
1
  class Agent < ApplicationRecord
2
2
  # Multi-tenant associations
3
+ <% if PropelAuthentication.configuration.organization_required? -%>
4
+ belongs_to :organization
5
+ <% end -%>
6
+ <% if PropelAuthentication.configuration.agency_required? -%>
3
7
  belongs_to :agency
8
+ <% end -%>
4
9
  belongs_to :user
5
10
 
11
+ <% if PropelAuthentication.configuration.agency_required? -%>
6
12
  validates :user_id, uniqueness: { scope: :agency_id }
13
+ <% else -%>
14
+ validates :user_id, uniqueness: { scope: :organization_id }
15
+ <% end -%>
7
16
  <% if @rendering_engine == 'json_facet' -%>
8
17
 
9
18
  # Facets
@@ -99,9 +99,11 @@ class Invitation < ApplicationRecord
99
99
  update!(status: :accepted)
100
100
 
101
101
  # Create agent relationship if needed
102
+ agency = self.organization.agencies.first || self.organization.agencies.create!(name: 'Default Agency')
102
103
  Agent.create!(
103
104
  user: user,
104
- agency: self.organization.agencies.first || self.organization.agencies.create!(name: 'Default Agency')
105
+ agency: agency,
106
+ organization: self.organization
105
107
  )
106
108
 
107
109
  user
@@ -2,6 +2,9 @@ class Organization < ApplicationRecord
2
2
  # Multi-tenant associations
3
3
  has_many :users, dependent: :destroy
4
4
  has_many :agencies, dependent: :destroy
5
+ <% if PropelAuthentication.configuration.agency_required? -%>
6
+ has_many :agents, dependent: :destroy
7
+ <% end -%>
5
8
 
6
9
  validates :name, presence: true
7
10
  <% if @rendering_engine == 'json_facet' -%>
@@ -44,7 +44,9 @@ module PropelAuthentication
44
44
  :support_email,
45
45
  :enable_email_notifications,
46
46
  :enable_sms_notifications,
47
- :agency_tenancy,
47
+ :tenancy_enabled,
48
+ :organization_required,
49
+ :agency_required,
48
50
  :require_organization_id,
49
51
  :require_user_id,
50
52
  :namespace,
@@ -105,9 +107,13 @@ module PropelAuthentication
105
107
  @enable_email_notifications = true
106
108
  @enable_sms_notifications = false # Requires SMS provider configuration
107
109
 
108
- # Tenancy configuration
109
- # Controls whether multi-agency tenancy is enabled within organizations
110
- @agency_tenancy = true # Enable agency-level tenancy by default
110
+ # Tenancy configuration - hierarchical structure
111
+ # Master switch: enables/disables entire multi-tenancy system
112
+ @tenancy_enabled = <%= tenancy_enabled? %> # <%= tenancy_enabled? ? 'Enable' : 'Disable' %> multi-tenancy system
113
+
114
+ # Individual tenancy model requirements (only effective when tenancy_enabled = true)
115
+ @organization_required = <%= organization_required? %> # <%= organization_required? ? 'Require' : 'Optional' %> organization association
116
+ @agency_required = <%= agency_required? %> # <%= agency_required? ? 'Enable' : 'Disable' %> agency-level tenancy
111
117
 
112
118
  # API tenancy requirements (for PropelApi integration)
113
119
  # Controls whether APIs require explicit tenancy context in requests
@@ -120,6 +126,19 @@ module PropelAuthentication
120
126
  @version = nil
121
127
  @auth_scope = nil
122
128
  end
129
+
130
+ # Helper methods for template conditionals
131
+ def tenancy_enabled?
132
+ @tenancy_enabled
133
+ end
134
+
135
+ def organization_required?
136
+ @tenancy_enabled && @organization_required
137
+ end
138
+
139
+ def agency_required?
140
+ @tenancy_enabled && @agency_required
141
+ end
123
142
  end
124
143
  end
125
144
 
@@ -141,11 +160,19 @@ PropelAuthentication.configure do |config|
141
160
  # User registration settings
142
161
  config.allow_registration = true
143
162
 
144
- # Multi-tenancy settings
163
+ # Multi-tenancy settings - hierarchical configuration
164
+ # Master switch: enables/disables entire multi-tenancy system
165
+ # When false: Single-tenant mode (no tenancy models required)
166
+ config.tenancy_enabled = <%= tenancy_enabled? %>
167
+
168
+ # Individual tenancy model requirements (only effective when tenancy_enabled = true)
169
+ # Controls whether users must belong to organizations
170
+ config.organization_required = <%= organization_required? %>
171
+
145
172
  # Controls whether agency-level tenancy is enabled within organizations
146
173
  # When true: Organization -> Agency -> Resources structure
147
174
  # When false: Organization -> Resources structure (simpler)
148
- config.agency_tenancy = true
175
+ config.agency_required = <%= agency_required? %>
149
176
 
150
177
  # API tenancy requirements (for PropelApi integration)
151
178
  # Controls whether clients must provide explicit tenancy context in API requests
@@ -11,7 +11,7 @@ class <%= controller_namespace('tokens') %>LockableIntegrationTest < ActionDispa
11
11
  organization: @organization
12
12
  )
13
13
  # Create agent association for agency access (required for real-time agency lookup)
14
- Agent.create!(user: @user, agency: @agency, role: "member")
14
+ Agent.create!(user: @user, agency: @agency, organization: @organization, role: "member")
15
15
  end
16
16
 
17
17
  # CRITICAL: Real API integration with lockable functionality
@@ -13,7 +13,7 @@ class <%= controller_namespace('passwords') %>PasswordResetIntegrationTest < Act
13
13
  last_name: "User"
14
14
  )
15
15
  # Create agent association for agency access (required for real-time agency lookup)
16
- Agent.create!(user: @user, agency: @agency, role: "member")
16
+ Agent.create!(user: @user, agency: @agency, organization: @organization, role: "member")
17
17
  end
18
18
 
19
19
  # POST <%= auth_route_prefix %>/reset - Request Password Reset Tests
@@ -51,9 +51,9 @@ class <%= controller_namespace('tokens') %>TokensControllerTest < ActionDispatch
51
51
  @agency = Agency.create!(name: "Main Agency", organization: @organization)
52
52
  @rival_agency = Agency.create!(name: "Rival Agency", organization: @rival_organization)
53
53
 
54
- Agent.create!(user: @valid_user, agency: @agency, role: "member")
55
- Agent.create!(user: @other_user, agency: @rival_agency, role: "member")
56
- Agent.create!(user: @inactive_user, agency: @agency, role: "member")
54
+ Agent.create!(user: @valid_user, agency: @agency, organization: @organization, role: "member")
55
+ Agent.create!(user: @other_user, agency: @rival_agency, organization: @rival_organization, role: "member")
56
+ Agent.create!(user: @inactive_user, agency: @agency, organization: @organization, role: "member")
57
57
  end
58
58
 
59
59
  test "POST <%= auth_route_prefix %>/login with valid credentials returns JWT token and user data" do
@@ -232,7 +232,7 @@ class <%= controller_namespace('tokens') %>TokensControllerTest < ActionDispatch
232
232
  }
233
233
 
234
234
  # VERIFY PARAMETER VALIDATION: Check required field enforcement
235
- assert_response :unprocessable_content, "Missing required parameters should return unprocessable_content"
235
+ assert_response :bad_request, "Missing required parameters should return bad_request"
236
236
  response_json = JSON.parse(response.body)
237
237
  assert response_json["error"].present?, "Should return validation error message"
238
238
  end
@@ -1,3 +1,3 @@
1
1
  module PropelAuthentication
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: propel_authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Propel Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-10 00:00:00.000000000 Z
11
+ date: 2025-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -110,8 +110,13 @@ files:
110
110
  - lib/generators/propel_authentication/templates/db/migrate/create_invitations.rb
111
111
  - lib/generators/propel_authentication/templates/db/migrate/create_organizations.rb
112
112
  - lib/generators/propel_authentication/templates/db/migrate/create_users.rb
113
- - lib/generators/propel_authentication/templates/db/seeds.rb
113
+ - lib/generators/propel_authentication/templates/db/propel_seeds.rb.tt
114
114
  - lib/generators/propel_authentication/templates/doc/signup_flow.md
115
+ - lib/generators/propel_authentication/templates/fixtures/agencies.yml.erb
116
+ - lib/generators/propel_authentication/templates/fixtures/agents.yml.erb
117
+ - lib/generators/propel_authentication/templates/fixtures/invitations.yml.erb
118
+ - lib/generators/propel_authentication/templates/fixtures/organizations.yml.erb
119
+ - lib/generators/propel_authentication/templates/fixtures/users.yml.erb
115
120
  - lib/generators/propel_authentication/templates/models/agency.rb.tt
116
121
  - lib/generators/propel_authentication/templates/models/agent.rb.tt
117
122
  - lib/generators/propel_authentication/templates/models/invitation.rb.tt