Difference Between SiteAdminController and TenantAdminController in Property Web Builder

The SiteAdminController scopes all actions to a single tenant website using subdomain-based isolation and subscription checks, while the TenantAdminController operates across all tenants without scoping, restricted only to a whitelist of super-admin emails.

The Property Web Builder repository (etewiah/property_web_builder) implements a multi-tenant architecture that separates day-to-day website management from platform-wide administration. Understanding the difference between site_admin and tenant_admin controllers is essential for developers extending the admin interface or debugging authorization issues in this Rails application.

Tenant Scoping and Data Isolation

The fundamental architectural distinction between these base controllers lies in how they handle multi-tenant data access and isolation boundaries.

SiteAdminController: Automatic Tenant Scoping

In app/controllers/site_admin_controller.rb, the controller includes the SubdomainTenant concern, which invokes set_tenant_from_subdomain to automatically set ActsAsTenant.current_tenant based on the request's subdomain. This mechanism ensures that all PwbTenant:: model queries automatically include a WHERE website_id = current_website.id clause, preventing cross-tenant data leakage.

  • Single-tenant queries: When inheriting from SiteAdminController, calling Pwb::Page.all returns only records belonging to the current website.
  • Security boundary: A compromised admin account can only affect the specific website identified by the subdomain, not the entire platform.

TenantAdminController: Unscoped Cross-Tenant Access

Conversely, app/controllers/tenant_admin_controller.rb deliberately excludes any tenant-scoping concerns to enable platform-wide administration. The controller provides an unscoped_model helper (defined in lines 37-41) that removes the default tenant filter, allowing queries across all websites.


# Querying all tenants from a TenantAdminController subclass

class TenantAdmin::WebsitesController < TenantAdminController
  def index
    @all_websites = unscoped_model(Pwb::Website).order(:created_at)
  end
end

Authentication and Authorization Models

Each controller implements distinct security models appropriate to their administrative scope.

Site-Level Admin Verification

SiteAdminController uses Devise helpers and requires the logged-in user to be an admin or owner of the current website via the user_is_admin_for_subdomain? check. This ensures that only users with explicit permissions for that specific tenant can access its admin panel.

Super-Admin Email Whitelist

TenantAdminController bypasses tenant-specific ownership checks and instead validates users against a whitelist defined in the TENANT_ADMIN_EMAILS environment variable. The tenant_admin_allowed? method (lines 47-52) checks if the current user's email appears in this privileged list before granting access to cross-tenant functionality.


# Authorization check in TenantAdminController

before_action :require_tenant_admin!

private

def require_tenant_admin!
  render_tenant_admin_forbidden unless tenant_admin_allowed?
end

Security Boundaries and Feature Gating

The controllers implement different bypass mechanisms and subscription logic reflecting their operational contexts.

Environment-Based Auth Bypass

Both controllers support the AdminAuthBypass concern, which skips authentication when the BYPASS_ADMIN_AUTH environment variable is set. However, SiteAdminController additionally includes DevSubscriptionBypass for development scenarios, while TenantAdminController relies solely on the standard bypass mechanism.

Subscription Access Control

SiteAdminController enforces commercial constraints by calling check_subscription_access before actions, ensuring the current website maintains an active subscription before allowing admin functionality. TenantAdminController intentionally omits subscription checks, as platform administrators must manage websites regardless of their billing status or plan tier.

Error Handling and UI Patterns

The controllers handle distinct failure modes appropriate to their user interfaces.

Record Not Found Handling

SiteAdminController rescues ActiveRecord::RecordNotFound exceptions to display user-friendly "record not found" pages within the single-tenant context, preserving the website's branded admin experience.

Parameter Missing and Logging

TenantAdminController specifically handles ActionController::ParameterMissing via the handle_parameter_missing method (lines 70-109). This provides detailed logging and returns structured JSON or HTML error responses, which is critical when managing forms across hundreds of potential tenant configurations.

Layout and Interface Differences

Each controller renders a distinct administrative interface:

  • SiteAdminController: Uses layout 'site_admin' for the single-tenant management dashboard tailored to individual website owners.
  • TenantAdminController: Uses layout 'tenant_admin' for the cross-tenant super-admin interface designed for platform operators.

Practical Implementation Examples

Accessing Scoped Data in Site Admin

When building features for individual website management, inherit from SiteAdminController to automatically scope all queries:

class SiteAdmin::PagesController < SiteAdminController
  def index
    @pages = Pwb::Page.all   # Automatically scoped to current_website

  end
end

Handling Missing Parameters in Tenant Admin

When processing cross-tenant forms that may have inconsistent parameter structures:

class TenantAdmin::ResourcesController < TenantAdminController
  def create
    @resource = Pwb::Resource.new(resource_params)
    if @resource.save
      redirect_to tenant_admin_resources_path, notice: 'Created'
    else
      render :new
    end
  end
  
  private
  
  def resource_params
    params.require(:resource).permit(:name, :content)
  end
end

If params.require(:resource) raises ActionController::ParameterMissing, TenantAdminController automatically rescues and logs the error via handle_parameter_missing.

Summary

  • Tenant Scope: SiteAdminController includes SubdomainTenant for automatic scoping; TenantAdminController uses unscoped_model for cross-tenant queries.
  • Authorization: Site admins verify ownership via user_is_admin_for_subdomain?; tenant admins validate against TENANT_ADMIN_EMAILS.
  • Subscription Logic: Only SiteAdminController enforces check_subscription_access for billing compliance.
  • Data Isolation: SiteAdminController queries automatically filter by website_id; TenantAdminController requires explicit unscoping.
  • Error Focus: Site admin handles missing records gracefully; tenant admin handles missing parameters with detailed logging.

Frequently Asked Questions

What is the main difference between SiteAdminController and TenantAdminController?

The primary difference is tenant scoping. SiteAdminController automatically scopes all database queries to a single website resolved from the request subdomain, while TenantAdminController operates without tenant scoping to manage data across all websites in the platform.

How does tenant scoping work in Property Web Builder?

In app/controllers/site_admin_controller.rb, the SubdomainTenant concern sets ActsAsTenant.current_tenant via set_tenant_from_subdomain. This activates the acts_as_tenant gem's automatic query filtering, ensuring all Pwb:: model queries include the current website's ID.

Who can access the TenantAdminController interface?

Only users whose email addresses appear in the TENANT_ADMIN_EMAILS environment variable can access TenantAdminController and its subclasses. This whitelist approach restricts cross-tenant administrative powers to a small set of trusted platform operators.

Why does TenantAdminController bypass subscription checks?

Tenant administrators manage the platform infrastructure itself, including websites with expired or inactive subscriptions. Requiring an active subscription for these super-admin actions would prevent necessary support and billing management, so TenantAdminController intentionally omits the check_subscription_access logic found in SiteAdminController.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →