Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi tenant Examples #260

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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('/')
Comment on lines +51 to +53
Copy link
Owner

@janko janko Dec 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed, BTW? Won't the overridden #prefix method automatically add the path_key?

It seems you have two sources of path_key – the attribute accessor and request.env. Maybe that's why it isn't working?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That shouldn't be there... It was one of my transitional hacks as I figured it out... Not sure why it is still there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a wiki page once I have it all setup and working!

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