Anand
Anand

Reputation: 3770

How to ensure at most one admin in rails app with devise?

My rails app can have at most one admin, but any number of users.

I am using the devise gem, and am using a User model, with a boolean admin field to determine if a user is an admin.

I want to ensure that at most one admin exists on the app instance. More specifically, I want to ensure that it is not possible to add another admin if an admin already exists on the site.

I currently have a weak solution in place that overrides the users/registrations/new devise view to allow a user to register as an admin, only if there are no admins in the database (otherwise only users can sign up).

<!-- in /users/registrations/new.html.erb --> 
<% if admin_exists? %>
    <h2>User Sign up</h2>
<% else %>
    <h2>Admin Sign up</h2>
<% end %>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

  ...

  <% unless admin_exists? %>  
      <%= f.hidden_field :admin, value: true%>
  <% end %>

  ...   

I need a stronger check than that, so I considered the following:

  1. Validations: I don't want to use ActiveRecord validations, since there are scenarios when validations are skipped, and allowing more than one admin would be disastrous.

  2. before_action: Based on this approach that adds filters to controllers, I could add a check that precludes adding an admin during creates and updates.

  3. Using triggers and stored procs (ugh!)

Update 1:

  1. Very similar to 2, using a check in ApplicationController as follows:

    class ApplicationController < ActionController::Base
      before_action :check_at_most_one_admin_on_writes, if: :devise_controller?, 
         only: [:create, :update] 
    
      private
        def check_at_most_one_admin_on_user_writes
          if(admin_exists && params.has_key?(:admin) && params[:admin] == true)
            flash[:alert] = "Site administrator already exists"
            redirect_to :back
          end
        end
    end
    

Update 2: (4) does not work. Likely requires overriding RegistrationsController directly.

I am still not sure if any of these is robust enough to guarantee that two admins will never get created inadvertently. What would be a good approach to enforce this at most one admin constraint?

Update 3: (based on comments below)

The app is a personal web site framework, that each customer downloads, deploys to a hoster and then immediately configures oneself as an admin. So, I cannot pre-create the admin through seeds or such.

Upvotes: 0

Views: 176

Answers (1)

Tim
Tim

Reputation: 2923

Assuming your boolean attribute maps to a database column which allows null, then you could add a unique index to it. Then, for the admin user, the column is set to 1 and only one row is permitted with that value. You then need to be sure that normal users have the column unset (left as null), as trying to set the column to 0 for multiple rows will fail due to the unique index. This means that the database itself will enforce the requirement for there to be only 1 admin user. It also avoids the need for triggers or stored procedures.

You can't generally rely on ruby code in your app to enforce the constraint via validations because these don't provide the integrity guarantees that the database can (e.g. situations with multiple threads/processes).

Upvotes: 1

Related Questions