[go: up one dir, main page]

Skip to content

Instantly share code, notes, and snippets.

@LEstradioto
Last active May 28, 2024 02:22
Show Gist options
  • Save LEstradioto/8685c23238e75ffde55e83332067a14d to your computer and use it in GitHub Desktop.
Save LEstradioto/8685c23238e75ffde55e83332067a14d to your computer and use it in GitHub Desktop.
devise + acts_as_tenant + subdomain scope

Minimal setup for devise + acts_as_tenant + subdomain scope

  • Devise subdomain scope has a strategy: https://github.com/heartcombo/devise/wiki/How-to:-Scope-login-to-subdomain

  • This setup let users be scoped for subdomain, like SASS clients

  • Register/login in different subdomains ex.: user1.domain.com, user2.domain.com

  • To use this strategy out of the box with Acts As Tenant gem, we need to adapt it a little

  • acts_as_tenant adds the possibility to use an Account table/model, or in this example a Subdomain table/model

class ApplicationController < ActionController::Base
set_current_tenant_by_subdomain(:subdomain, :subdomain)
before_action :authenticate_user!
before_action :rewrite_param_names, if: :devise_controller?
private
# shortcut manner to ALWAYS add subdomain_id in devise requests without needing to patch multiples devises controllers
def rewrite_param_names
if request.params[:user].present?
request.params[:user][:subdomain_id] = request.subdomain
end
end
end
class CreateSubdomains < ActiveRecord::Migration[7.1]
def change
create_table :subdomains do |t|
t.string :subdomain, null: false
t.timestamps
end
add_index :subdomains, :subdomain, unique: true
end
end
Rails.application.routes.draw do
# constraints to only available/registered subdomains if not redirect to root/guest
constraints(lambda { |req| Subdomain.exists?(subdomain: req.subdomain) }) do
devise_scope :user do
authenticated :user do
root "dashboard#show", as: :authenticated_root
end
unauthenticated do
root "devise/sessions#new", as: :unauthenticated_root
end
end
end
# in this controller ensure no subdomains are present and redirect
root to: "guests#home"
end
# frozen_string_literal: true
class DeviseCreateUsers < ActiveRecord::Migration[7.1]
def change
# .......... normal Devise migration
# It changes only this part
add_reference :users, :subdomain, foreign_key: true
add_index :users, [:email, :subdomain_id], unique: true
end
end
class Subdomain < ApplicationRecord
has_many :users, dependent: :destroy
validates :subdomain, presence: true, uniqueness: true, length: {maximum: 63}, format: {with: /\A[a-z0-9]+(-[a-z0-9]+)*\z/, message: "must be lowercase alphanumeric and can include hyphens"}
end
class User < ApplicationRecord
acts_as_tenant(:subdomain)
# :validatable is not included because Devise subdomain constraint
# so we just copy validations from devise module
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :confirmable, :lockable,
:trackable, authentication_keys: [:email, :subdomain_id]
validates :email, presence: true
# scope it by subdomain_id
validates :email, uniqueness: {scope: :subdomain_id, allow_blank: true, case_sensitive: true, if: :devise_will_save_change_to_email?}
validates :email, format: {with: Devise.email_regexp, allow_blank: true, if: :devise_will_save_change_to_email?}
validates :password, presence: {if: :password_required?}
validates :password, confirmation: {if: :password_required?}
validates :password, length: {within: Devise.password_length, allow_blank: true}
# patch Devise Subdomain Scoped to work with acts_as_tenant
def self.find_for_authentication(warden_conditions)
subdomain_id = Subdomain.find_by(subdomain: warden_conditions[:subdomain_id]).id
where(email: warden_conditions[:email], subdomain_id: subdomain_id).first
end
protected
def password_required?
!persisted? || !password.nil? || !password_confirmation.nil?
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment