Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

New starter application for BaseCamp like subdomains using Devise/Invitable and CanCan

branch: master
README.textile

Rails3-Devise-BaseCampLike-Subdomains-With-CanCan-and-Invitable

Note: The 3.1 version has not been fully tested. Several things changed in 3.1 (named url_for allows subdomain and host does not contain port number). There were other problems with changes to Devise and Devise invitable that seem to be worked around, but I’ve actually stopped using Devise and rolled my own using the approach on Railscast. I will try to verify the changes in Devise, but not right away. Steve

Use this project as a starting point for a Rails 3 application that uses basecamp-like subdomains and authentication. User management and authentication is implemented using Devise.

Authorization is implemented using CanCan.

Invitations are implement using Devise Invitable

As a starting point, all Devise options are not implemented. eMail is restricted to local host. Confirmable is not used, but should be turned on once mail is set up. There are also features that should be removed when used for a real application. From the root domain you can list all users and subdomains(accounts) and visit those users or subdomains.

This got its start with fortuity/Rails3-Subdomain-Devise that provided a subdomain support starting point (any user could create a new subdomain).

The initial basecamp like version was a fork of the above. salex/Rails3-Subdomain-Devise.

While that version works, there were some business rules I wanted to implement that made it more like basecamp. To implement those rules, CanCan and Devise Invitable are used. Code was also stollen from links on the Devise wiki to implement several features, such as creating a login name.

There are many ways to skin a cat!

This, probably politically incorrect idiom, is way of saying there a many ways to define and use subdomains. I’ve decided to clean up some stuff in the readme and to define some of those ways and how they apply to this demo shell.

My interpretation of Basecamp Like:

  • When you sign-up at the root domain, you must provide a subdomain or account name.
    • If the account name is unique, you become the owner of that account
  • Know one else can sign up to that account
  • You can invite other users to access the account
  • If you login at the basecamp root domain, you are redirected to your account
  • Your session is restricted to your subdomain – you can’t “visit” other accounts

That was my original starting point, except the initial version didn’t have the capability to login at the root domain and be redirected. I hacked my way through that, but then I kept getting suggestions and had my own ideas and problems with Devise.

  • What if the subdomain is kind of blog like and I don’t mind other users joining (sign up instead of invite)
  • Devise is kind of set in its ways!
    • If you try to access a page requiring authentication, it wants to take you to a sign in page. I never really liked that approach and decided to just go to the subdomain root and display a flash message.
    • After you sign in, it wants to take you to the page you originally tried to access. Storing redirects is nice, unless you have someone mucking with the url and trying to break your supposedly well though out scheme!

Fortunately you can override some of the defaults – but then some people like the defaults. Unfortunately, adding some of these features (and not knowing how to write tests) often breaks something else! I’d like to say that everything is perfect, but then I’m not a Rails expert. This describes the current version.

Configuration

In config/application.rb there are two configuration options for this shell:

# Custome application configuration
  config.allow_account_sign_up = false
   # Set to true if you want to allow users to sign up to an account/subdomain versus being invited
  config.authenticate_to_home = true
   # Set to false if you want redirected to sign_in page on authenticate_user! Leaving true redirects to account home with flash alert

Helpers

is_root_domain? 
	# return true if there is no subdomain
can_sign_up? 
	# return true if config.allow_account_sign_up is set to true
	# Used in conjection with root_domain? for root domain.
current_account 
	# If subdomain is present, returns the account, else nil
is_account_resource?(account_id) 
	# call in controller before_filter to make sure the resource belongs to the account
	# used to prevent url modification to resources that are not owned by account.

Overrides/hooks

authenticate_user!
	# An override to devise to redirect to custom page if configured to do so 
	# config.authenticate_to_home = true
authenticate_inviter!
	# A hook to Devise_invitable that uses CanCan to see if a user is authorized 
	# to invite another user.
after_sign_in_path_for(resource_or_scope)
	# Modified to redirect user to their account root if login is at root domain
	# User is signed out of that domain and signed in to their domain using a token.
sign_in_and_redirect(resource_or_scope, resource=nil)
	# Handles case if user is visiting another subdomain and tries to sign in.
	# Also handles the redirect on sign up, sending them to their account root.

Testing (Notes to me)

  • rake db:drop and then follow install instructions
  • set defaults
    • config.allow_account_sign_up = false
    • config.authenticate_to_home = true
  • root domain
    • sign up and login links visible
    • try to access /members => authentication error
    • try to access /uses/invitation/new => unauthorized error
    • try sign-up, new account, logs in a redirects to new account home
    • try login user1-please, logs in a redirects to account home
    • try login user1-badpwd, form error
  • foo subdomain
    • only login link
    • /users/sign_up => error, sign up not permitted from subdomain
    • try to access /uses/invitation/new => unauthorized error
    • try to access /members => authentication error
    • try login user1-please, logs account home => members and invite user links
    • logout and login user2-please, logs in account home => invite user links
    • try to access /members => authentication error
    • logout and login user4-please, logs in bar account home => not invite or member links
  • set defaults and restart
    • config.allow_account_sign_up = true
    • config.authenticate_to_home = false
    • logout bar subdomain → should see sign_up link → sign up should add new user to subdomain
    • logout and try to access /members => authentication error redirect to login page
    • try login user4-please => error not authorized
  • root domain
    • try members → login screen
    • try login user3-please → redirects to root, no unauthorized flash
    • logout and login user1-please, logs in account home
    • open members table and show member user1
    • modify url to try to show member 2 = not your resource error

The rules (and how they changed)

  • You can only register or sign-up at the root domain. Configurable
    • The application will generate an alert if sign-in is attempted from a subdomain Configurable
    • Sign-in requires a subdomain or account name. It must be unique and non blank.
    • If the account name is unique, the account is created and the new user becomes the site admin
  • New users must be invited by the site admin to register for that subdomain Configurable
  • Login can be either by eMail and login, which is the email name if unique.
  • Login is unique across all subdomains
  • A user who tries to login to another subdomain will be rejected Redirected to their account
  • The site admin can edit members, which is a model inherited from user
    • Members link added to menubar if site admin.
    • Site admin can set roles on the edit page.
    • Roles implementation is just a simple text field and check with include?, so should be unique. Roll your own if you need more.
  • A user who logs into the root domain, will be signed off the root domain, redirected to their domain with a token, and logged in to their subdomain.
  • There are no cross domain sessions, you can only start a session in your subdomain.
  • Attempts at url modifications (show member/user from another domain) should be rejected by CanCan

Basic models

	class Account < ActiveRecord::Base
	  has_many :users
		#name is subdomain name
	end
	class User < ActiveRecord::Base
	  belongs_to :account
	end
	class Member < User
		#used for admin to manage users
	end
	class Site < Account
		#used to define a site(subdomain) home page that differs from the root domain home page. 
	end
	

Installation

  • git clone or fork
  • bundle install
  • rake db:create
  • rake db:schema:load
  • rake db:seed

Disclaimer

Everyone knows that engineers can’t write… I is an engineer! That includes not only English, but code.

Rails and Ruby has been a trip from someone who started with Fortran, Basic and 6502 assembler. I don’t consider myself a novice, but certainly not an expert. I tried to solicit help in some areas, like is:

 def after_sign_in_path_for(resource_or_scope)
   # Modified to redirect user to their account root if login is at root domain
   # User is signed out of that domain and signed in to their domain using a token.
   scope = Devise::Mapping.find_scope!(resource_or_scope)
   account_name = current_user.account.name
   if current_account.nil? 
     # logout of root domain and login by token to account
     token =  Devise.friendly_token
     current_user.loginable_token = token
     current_user.save
     sign_out(current_user)
     flash[:notice] = nil
     home_path = valid_user_url(token, :account => account_name)
     return home_path 
   end
   super
 end

the best way to redirect a sign-in from the root domain to a users subdomain. It works, but not sure if there are better ways.

There are probably a few other areas where code can be improved. You are welcome to fork the project and add any improvements and send me a pull request.

If you add any features or different set of business rules, I may just point to your fork.

While this is just a demo, I couldn’t stand no CSS. I added just a little, mainly a reset and then a few things for lists etc.

License

Public Domain Dedication

This work is a compilation and derivation from other previously released works. With the exception of various included works, which may be restricted by other licenses, the author or authors of this code dedicate any and all copyright interest in this code to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this code under copyright law.

Something went wrong with that request. Please try again.