Skip to content

Commit

Permalink
✅ Examples for multi-tenant support
Browse files Browse the repository at this point in the history
  • Loading branch information
pboling committed Dec 22, 2023
1 parent b8a1cce commit 4989c3e
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 7 deletions.
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby 2.7.8
26 changes: 26 additions & 0 deletions test/integration/email_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ class EmailTest < IntegrationTest
assert_includes email.body.to_s, "Someone has created an account with this email address"
end

test "mailer delivery - multi-tenant" do
register(login: "[email protected]", prefix: "/multi/tenant/animal-farm")

assert_equal 1, ActionMailer::Base.deliveries.count

email = ActionMailer::Base.deliveries[0]

assert_equal "[email protected]", email[:to].to_s
assert_equal "[email protected]", email[:from].to_s
assert_equal "[RodauthTest] Verify Account", email[:subject].to_s

assert_includes email.body.to_s, "Someone has created an account with this email address"
end

test "verify login change email" do
register(login: "[email protected]", password: "secret", verify: true)

Expand All @@ -26,4 +40,16 @@ class EmailTest < IntegrationTest

assert_equal 2, ActionMailer::Base.deliveries.count
end

test "verify login change email - multi-tenant" do
register(login: "[email protected]", password: "secret", prefix: "/multi/tenant/kiwi", verify: true)

visit "/change-email"

fill_in "Login", with: "[email protected]"
fill_in "Password", with: "secret"
click_on "Change Login"

assert_equal 2, ActionMailer::Base.deliveries.count
end
end
42 changes: 42 additions & 0 deletions test/rails_app/app/mailers/rodauth_multi_tenant_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class RodauthMultiTenantMailer < ActionMailer::Base
default to: -> { @rodauth.email_to }, from: -> { @rodauth.email_from }

def verify_account(name, account_id, path_key, key)
@rodauth = rodauth(name, account_id, path_key) { @verify_account_key_value = key }
@account = @rodauth.rails_account

mail(subject: @rodauth.email_subject_prefix + @rodauth.verify_account_email_subject)
end

def reset_password(name, account_id, path_key, key)
@rodauth = rodauth(name, account_id, path_key) { @reset_password_key_value = key }
@account = @rodauth.rails_account

mail(subject: @rodauth.email_subject_prefix + @rodauth.reset_password_email_subject)
end

def verify_login_change(name, account_id, path_key, key)
@rodauth = rodauth(name, account_id, path_key) { @verify_login_change_key_value = key }
@account = @rodauth.rails_account
@new_email = @account.login_change_key.login

mail(to: @new_email, subject: @rodauth.email_subject_prefix + @rodauth.verify_login_change_email_subject)
end

def password_changed(name, account_id, path_key)
@rodauth = rodauth(name, account_id, path_key)
@account = @rodauth.rails_account

mail(subject: @rodauth.email_subject_prefix + @rodauth.password_changed_email_subject)
end

# ...
private
def rodauth(name, account_id, path_key, &block)
instance = RodauthApp.new({ path_key: path_key }).rodauth(name)
instance.path_key = path_key
instance.instance_eval { @account = account_ds(account_id).first! }
instance.instance_eval(&block) if block
instance
end
end
21 changes: 21 additions & 0 deletions test/rails_app/app/misc/rodauth_app.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
class RodauthApp < Rodauth::Rails::App
configure RodauthMain
configure RodauthAdmin, :admin
configure RodauthMultiTenant, :multi_tenant

plugin :symbol_matchers

# Allow UUID characters in path_key
symbol_matcher :path_key, /([A-Z0-9_-]+)/xi

configure(:jwt) do
enable :jwt, :create_account, :verify_account
Expand All @@ -24,6 +30,21 @@ class RodauthApp < Rodauth::Rails::App

r.rodauth
r.rodauth(:admin)
r.on("multi/tenant") do
r.on(:path_key) do |path_key|
# In a real life application you'd only proceed to rodauth routing if a tenant was found.
# We'll mimic that possibility by skipping a /banana path-key
next if path_key == "banana"

r.env[:path_key] = path_key
rodauth(:multi_tenant).path_key = path_key

r.rodauth(:multi_tenant)
next
end
next
end

r.on("jwt") { r.rodauth(:jwt) }
r.on("json") { r.rodauth(:json) }

Expand Down
68 changes: 68 additions & 0 deletions test/rails_app/app/misc/rodauth_multi_tenant.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class RodauthMultiTenant < Rodauth::Rails::Auth
configure do
enable :create_account, :verify_account, :verify_account_grace_period,
:login, :remember, :logout, :active_sessions,
:reset_password, :change_password, :change_password_notify,
:change_login, :verify_login_change,
:close_account, :lockout, :recovery_codes, :internal_request,
:path_class_methods, :jwt

prefix { "/multi/tenant/#{path_key}" }

rails_controller { RodauthController }

before_rodauth do
if param_or_nil("raise")
raise NotImplementedError
elsif param_or_nil("fail")
fail "failed"
end
end

account_status_column :status

email_subject_prefix "[RodauthTest] "
email_from "[email protected]"
create_reset_password_email do
RodauthMultiTenantMailer.reset_password(:multi_tenant, account_id, request.env[:path_key], reset_password_key_value)
end
create_verify_account_email { RodauthMultiTenantMailer.verify_account(:multi_tenant, account_id, request.env[:path_key], verify_account_key_value) }
create_verify_login_change_email { |_login| RodauthMultiTenantMailer.verify_login_change(:multi_tenant, account_id, request.env[:path_key], verify_login_change_key_value) }
create_password_changed_email { RodauthMultiTenantMailer.password_changed(:multi_tenant, account_id, request.env[:path_key]) }

require_login_confirmation? false
verify_account_set_password? false
extend_remember_deadline? true
max_invalid_logins 3

if defined?(::Turbo)
after_login_failure do
if rails_request.format.turbo_stream?
return_response rails_render(turbo_stream: [turbo_stream.append("login-form", %(<div id="turbo-stream">login failed</div>))])
end
end
check_csrf? { rails_request.format.turbo_stream? ? false : super() }
end

after_login { remember_login }

logout_redirect { rails_routes.root_path }
login_redirect do
segs = login_path.split('/')
segs.insert(-2, request.env[:path_key])
segs.join('/')
end
verify_account_redirect { login_redirect }
reset_password_redirect do
segs = login_path.split('/')
segs.insert(-2, request.env[:path_key])
segs.join('/')
end
title_instance_variable :@page_title

verify_login_change_route nil
change_login_route "change-email"
end

attr_accessor :path_key
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Someone (hopefully you) has changed the password for the account
associated to this email address.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Someone has requested a password reset for the account with this email
address. If you did not request a password reset, please ignore this
message. If you requested a password reset, please go to
<%= @rodauth.reset_password_email_link %>
to reset the password for the account.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Someone has created an account with this email address. If you did not create
this account, please ignore this message. If you created this account, please go to
<%= @rodauth.verify_account_email_link %>
to verify the account.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Someone with an account has requested their login be changed to this email address:

Old email: <%= @account.email %>

New email: <%= @new_email %>

If you did not request this login change, please ignore this message. If you
requested this login change, please go to
<%= @rodauth.verify_login_change_email_link %>
to verify the login change.
14 changes: 7 additions & 7 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,31 +49,31 @@ class IntegrationTest < ActionDispatch::IntegrationTest
include Capybara::DSL
include TestSetupTeardown

def register(login: "[email protected]", password: "secret", verify: false)
visit "/create-account"
def register(login: "[email protected]", password: "secret", verify: false, prefix: "")
visit "#{prefix}/create-account"
fill_in "Login", with: login
fill_in "Password", with: password
fill_in "Confirm Password", with: password
click_on "Create Account"

if verify
email = ActionMailer::Base.deliveries.last
verify_account_link = email.body.to_s[%r{/verify-account\S+}]
verify_account_link = email.body.to_s[%r{#{prefix}/verify-account\S+}]

visit verify_account_link
click_on "Verify Account"
end
end

def login(login: "[email protected]", password: "secret")
visit "/login"
def login(login: "[email protected]", password: "secret", prefix: "")
visit "#{prefix}/login"
fill_in "Login", with: login
fill_in "Password", with: password
click_on "Login"
end

def logout
visit "/logout"
def logout(prefix: "")
visit "#{prefix}/logout"
click_on "Logout"
end

Expand Down

0 comments on commit 4989c3e

Please sign in to comment.