Skip to content

Conversation

@shawntz
Copy link

@shawntz shawntz commented Jan 3, 2026

Description

Adds an admin setting to disable new account signups on self-hosted Fizzy instances. Useful for private deployments where account creation should be restricted.

Motivation

Self-hosted Fizzy installations often serve closed teams or personal use cases where unrestricted signups aren't desirable. Currently, there's no way to prevent new account creation without modifying code. This adds a simple toggle in account settings.

Implementation

Database

  • Adds signups_disabled boolean to accounts table (defaults to false, preserving current behavior)
  • Uses first account's setting as global configuration (appropriate for self-hosted single-tenant usage)

Controller & Model

  • Account.accepting_signups? checks the database setting instead of always returning false
  • Account settings controller permits signups_enabled parameter
  • Virtual attribute signups_enabled inverts signups_disabled for better UX
  • Turbo-powered form for immediate updates

Views

  • New toggle in Account Settings: "Allow New Account Signups"
  • Uses Fizzy's standard switch component (matches existing toggle style)
  • Positive phrasing: ON/blue = signups allowed, OFF/gray = signups disabled
  • Login page already conditionally shows signup link via Account.accepting_signups?
  • Admin-only access via existing authorization

Screenshot 2026-01-02 at 4 13 53 PM
Screenshot 2026-01-02 at 4 13 50 PM
Screenshot 2026-01-02 at 4 14 02 PM

Backward Compatibility

  • Existing deployments unaffected (defaults to false = signups enabled)
  • No environment variables or configuration changes required
  • Existing users can always sign in regardless of setting

Testing

Verified:

  • Signup link hidden when enabled
  • New account creation blocked when enabled
  • Existing user authentication works in both states
  • Setting persists correctly
  • Admin-only access enforced

Migration

bin/rails db:migrate

Adds nullable boolean column with default value.

Summary

Introduces a signups_disabled boolean to accounts, with a virtual signups_enabled attribute for improved UX. Adds UI controls in account settings to toggle new account signups, updates controller and model logic, and supports Turbo Stream updates. Also updates schema and includes a migration for the new column.

Introduces a signups_disabled boolean to accounts, with a virtual signups_enabled attribute for improved UX. Adds UI controls in account settings to toggle new account signups, updates controller and model logic, and supports Turbo Stream updates. Also updates schema and includes a migration for the new column.
Copilot AI review requested due to automatic review settings January 3, 2026 00:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an administrative setting to enable or disable new account signups for self-hosted Fizzy installations. The implementation adds a signups_disabled boolean column to the accounts table with a corresponding signups_enabled virtual attribute for better UX, updates the signup flow logic to respect this setting, and provides a toggle UI in the account settings page.

Key Changes:

  • Database schema updated with signups_disabled boolean field (defaults to false to preserve current behavior)
  • Account.accepting_signups? method modified to check the global setting from the first account
  • Account settings UI extended with a Turbo-powered toggle to control signup availability

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
db/migrate/20251231075247_add_signups_disabled_to_accounts.rb Adds migration to create the signups_disabled column on accounts table
db/schema_sqlite.rb Updates SQLite schema to reflect new signups_disabled column
app/models/account/multi_tenantable.rb Modifies accepting_signups? to check signups_disabled setting
app/models/account.rb Adds signups_enabled virtual attribute and modifies system_user method
app/controllers/account/settings_controller.rb Updates controller to handle signups_enabled parameter and support Turbo Stream responses
app/views/account/settings/show.html.erb Integrates new signups partial into settings page
app/views/account/settings/_signups.html.erb Implements toggle UI for controlling signup availability
app/views/account/settings/update.turbo_stream.erb Adds Turbo Stream response for live updates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


class_methods do
def accepting_signups?
multi_tenant || Account.none?
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

This line evaluates the expression but doesn't return it, making the code below unreachable in certain cases. The original logic "multi_tenant || Account.none?" should be checked and if true, should return true before checking signups_disabled. This should be: "return true if multi_tenant || Account.none?"

Suggested change
multi_tenant || Account.none?
return true if multi_tenant || Account.none?

Copilot uses AI. Check for mistakes.
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.2].define(version: 2025_12_10_054934) do
ActiveRecord::Schema[8.2].define(version: 2026_01_02_203805) do
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The migration filename timestamp is 20251231075247 but the schema version is 2026_01_02_203805. These should match. Migration files must have timestamps that match their version number in the schema.

Suggested change
ActiveRecord::Schema[8.2].define(version: 2026_01_02_203805) do
ActiveRecord::Schema[8.2].define(version: 2025_12_31_075247) do

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +42
users.find_or_create_by!(role: :system) do |user|
user.name = "System"
end
end

# Virtual attribute to invert signups_disabled for better UX
def signups_enabled
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

This change from find_by! to find_or_create_by! is not mentioned in the PR description and appears to be an unrelated change. If this is intentional, it should be in a separate PR or clearly documented in the PR description. The change modifies the behavior of system_user to automatically create the system user if it doesn't exist, which is a significant behavioral change beyond the scope of adding signup controls.

Suggested change
users.find_or_create_by!(role: :system) do |user|
user.name = "System"
end
end
# Virtual attribute to invert signups_disabled for better UX
def signups_enabled
users.find_by!(role: :system)
end
# Virtual attribute to invert signups_disabled for better UX
def signups_enabled
# Virtual attribute to invert signups_disabled for better UX
def signups_enabled

Copilot uses AI. Check for mistakes.
Comment on lines +41 to 48
# Virtual attribute to invert signups_disabled for better UX
def signups_enabled
!signups_disabled
end

def signups_enabled=(value)
self.signups_disabled = !ActiveModel::Type::Boolean.new.cast(value)
end
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The new signups_disabled functionality lacks test coverage. Given that the repository has comprehensive test coverage for other Account model features (as seen in test/models/account_test.rb and test/models/account/multi_tenantable_test.rb), tests should be added to verify:

  1. The signups_enabled virtual attribute correctly inverts signups_disabled
  2. Setting signups_enabled=true sets signups_disabled to false and vice versa
  3. The accepting_signups? method respects the signups_disabled setting
  4. The controller properly updates the signups_enabled parameter

Additionally, the existing tests in test/models/account/multi_tenantable_test.rb will likely fail due to the bug in line 10 of accepting_signups? and should be updated to cover the new behavior.

Copilot uses AI. Check for mistakes.
t.datetime "created_at", null: false
t.bigint "external_account_id"
t.string "name", limit: 255, null: false
t.boolean "signups_disabled", default: false, null: false
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The PostgreSQL schema file (db/schema.rb) has not been updated to include the signups_disabled column, while the SQLite schema (db/schema_sqlite.rb) has been updated. Both schema files should be kept in sync. After running the migration on both databases, both schema files should be regenerated to reflect the new column.

Suggested change
t.boolean "signups_disabled", default: false, null: false

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +15
# Check if signups are globally disabled (using first account's setting as global config)
first_account = Account.order(created_at: :asc).first
return true if first_account.nil?

!first_account.signups_disabled
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The accepting_signups? method queries the database (Account.order(created_at: :asc).first) on every call. Since this is called in views (sessions/new.html.erb) and controllers (SessionsController, SignupsController), and the setting changes infrequently, consider caching this value using Rails.cache or a class variable that's invalidated when the setting changes. This would improve performance, especially on high-traffic login pages.

Copilot uses AI. Check for mistakes.
@robinbrandt
Copy link
Contributor

robinbrandt commented Jan 3, 2026

Isn't this what

- `env/clear/MULTI_TENANT`: Set to `true` if you want to allow multiple accounts to sign up on your server (by default, Fizzy will allow you to create a single account).
is for?

@shawntz
Copy link
Author

shawntz commented Jan 4, 2026

Isn't this what

- `env/clear/MULTI_TENANT`: Set to `true` if you want to allow multiple accounts to sign up on your server (by default, Fizzy will allow you to create a single account).

is for?

@robinbrandt -- isn't this only for deployment with kamal? my feature addition creates a on/off switch for sign ups through the UI and is agnostic to the deployment method. though, let me know if i'm missing something. thx!

@robinbrandt
Copy link
Contributor

robinbrandt commented Jan 4, 2026

@shawntz the setting can be used with any deployment system, not only Kamal. That's just an environment variable you can set.

The code base is already interpreting the environment variable correctly and disables signups when ENV['MULTI_TENANT'] != 'true'.

Account.multi_tenant = ENV["MULTI_TENANT"] == "true" || config.x.multi_tenant.enabled == true

module Account::MultiTenantable
extend ActiveSupport::Concern
included do
cattr_accessor :multi_tenant, default: false
end
class_methods do
def accepting_signups?
multi_tenant || Account.none?
end
end
end

See #2035 for the PR that introduced this and the conversation with the Basecamp folks.

@kevinmcconnell
Copy link
Collaborator

Thanks for the contribution @shawntz. However, as @robinbrandt says, we already support controlling this using the MULTI_TENANT env var. The env approach works well for this since it's really an installation-specific setting. I don't think adding UI for this is necessary.

That setting is also documented in the general Docker deployment guide, but you can use it no matter how you choose to deploy Fizzy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants