diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..59511e1 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 2.7.8 diff --git a/test/integration/email_test.rb b/test/integration/email_test.rb index 85d40bb..c583cf1 100644 --- a/test/integration/email_test.rb +++ b/test/integration/email_test.rb @@ -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: "user@example.com", prefix: "/multi/tenant/animal-farm") + + assert_equal 1, ActionMailer::Base.deliveries.count + + email = ActionMailer::Base.deliveries[0] + + assert_equal "user@example.com", email[:to].to_s + assert_equal "noreply@rodauth.test", 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: "user@example.com", password: "secret", verify: true) @@ -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: "user@example.com", password: "secret", prefix: "/multi/tenant/kiwi", verify: true) + + visit "/change-email" + + fill_in "Login", with: "new@example.com" + fill_in "Password", with: "secret" + click_on "Change Login" + + assert_equal 2, ActionMailer::Base.deliveries.count + end end diff --git a/test/rails_app/app/mailers/rodauth_multi_tenant_mailer.rb b/test/rails_app/app/mailers/rodauth_multi_tenant_mailer.rb new file mode 100644 index 0000000..2313143 --- /dev/null +++ b/test/rails_app/app/mailers/rodauth_multi_tenant_mailer.rb @@ -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 diff --git a/test/rails_app/app/misc/rodauth_app.rb b/test/rails_app/app/misc/rodauth_app.rb index 0e2285a..58e0ef3 100644 --- a/test/rails_app/app/misc/rodauth_app.rb +++ b/test/rails_app/app/misc/rodauth_app.rb @@ -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 @@ -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) } diff --git a/test/rails_app/app/misc/rodauth_multi_tenant.rb b/test/rails_app/app/misc/rodauth_multi_tenant.rb new file mode 100644 index 0000000..8399d0f --- /dev/null +++ b/test/rails_app/app/misc/rodauth_multi_tenant.rb @@ -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 "noreply@rodauth.test" + 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", %(