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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -0
- data/lib/generators/propel_authentication/install_generator.rb +188 -240
- data/lib/generators/propel_authentication/templates/auth/passwords_controller.rb.tt +1 -4
- data/lib/generators/propel_authentication/templates/auth/signup_controller.rb.tt +3 -6
- data/lib/generators/propel_authentication/templates/auth/tokens_controller.rb.tt +2 -5
- data/lib/generators/propel_authentication/templates/core/configuration_methods.rb +38 -1
- data/lib/generators/propel_authentication/templates/db/migrate/create_agencies.rb +2 -0
- data/lib/generators/propel_authentication/templates/db/migrate/create_agents.rb +6 -1
- data/lib/generators/propel_authentication/templates/db/migrate/create_users.rb +2 -0
- data/lib/generators/propel_authentication/templates/db/propel_seeds.rb.tt +145 -0
- data/lib/generators/propel_authentication/templates/doc/signup_flow.md +9 -4
- data/lib/generators/propel_authentication/templates/fixtures/agencies.yml.erb +23 -0
- data/lib/generators/propel_authentication/templates/fixtures/agents.yml.erb +71 -0
- data/lib/generators/propel_authentication/templates/fixtures/invitations.yml.erb +9 -0
- data/lib/generators/propel_authentication/templates/fixtures/organizations.yml.erb +21 -0
- data/lib/generators/propel_authentication/templates/fixtures/users.yml.erb +77 -0
- data/lib/generators/propel_authentication/templates/models/agency.rb.tt +4 -0
- data/lib/generators/propel_authentication/templates/models/agent.rb.tt +9 -0
- data/lib/generators/propel_authentication/templates/models/invitation.rb.tt +3 -1
- data/lib/generators/propel_authentication/templates/models/organization.rb.tt +3 -0
- data/lib/generators/propel_authentication/templates/propel_authentication.rb.tt +33 -6
- data/lib/generators/propel_authentication/templates/test/controllers/auth/lockable_integration_test.rb.tt +1 -1
- data/lib/generators/propel_authentication/templates/test/controllers/auth/password_reset_integration_test.rb.tt +1 -1
- data/lib/generators/propel_authentication/templates/test/controllers/auth/tokens_controller_test.rb.tt +4 -4
- data/lib/propel_authentication.rb +1 -1
- metadata +8 -3
- 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
|
-
|
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
|
-
|
15
|
+
Multi-tenancy settings can be configured in `config/initializers/propel_authentication.rb`:
|
16
16
|
|
17
17
|
```ruby
|
18
|
-
|
19
|
-
|
20
|
-
|
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,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:
|
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
|
-
:
|
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
|
-
#
|
110
|
-
@
|
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.
|
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 :
|
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
|
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.
|
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-
|
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/
|
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
|