Devise + STI + Authorization - ruby-on-rails

I have the following situation:
My app has several types of users: Owner, Team Member, Collaborator, Client, and Guest. (the Guest type isn't relevant for this question)
These are the connections between the different models:
Owner belongs_to Account
Account has_many Team Members
Account has_many Projects
Projects habtm (or hmt) Collaborators
Project habtm (or mht) Clients
There are four key functions I need for my authentication & authorization:
Be able to upgrade a Collaborator to a Team Member (this means removing all habtm's to Projects, and add a belongs_to to Account
Be able to have different Devise strategies for different users (Owners have registerable, clients do not)
Have different login pages which only accept a subset of users (separate login for owner/team members/collaborators and clients)
Be able to call the different subset of users using Account.owner, Account.team_members, Project.collaborators, and Project.clients
I have gone over several solutions in my head, but I am unsure which would work best in my situation.
At first I thought about using Devise for both the authorization and authentication, but I figured I'd better use something like CanCan for the authorization part.
I also considered using one table for each user type, but that would make it harder to change roles after initial creation
I am now dubbing on using STI fo have TeamMember < User, Client < User, etc. But since Clients and Collaborators belong to Projects and Owners and Team Members belong to an Account, I am not sure that will work easily with STI, and I also haven't really found any good examples on Devise with STI.
Any ideas how to solve this situation?

Related

How can you set up a multi-tenant Rails app without using subdomains?

I'm trying to create a SAAS e-commerce tool with a backend for staff that also allows customers to have accounts and checkout on the front end. I'm struggling with how to design this so that the Company, Account Owners, Staff, and Customers are all siloed off to each Company, while also having the appropriate restrictions based on their roles.
From what I've read so far most of the rails solutions use multi-tenant patterns with subdomains, such as the Apartment gem, to silo off accounts. But it seems simpler to just have your site use one big app and database. For instance Basecamp recently switched to this approach with Basecamp3. Newer apps seem to be built this way.
And, should the admin features and the customer accounts / front end shop be separate apps completely, or can you do this with a "majestic monolith"? One big app and database, while large, seems more straight forward to me.
I found this blog post that explains how to do something like this with Pundit, but I'm still having trouble groking the big picture of how this could work with Account Owners, Staff, and Customers all in the same app.
Here are the basic needs for my app:
User Roles
Account Owner (creates the company's account and has full access to their company's data)
Staff (invited to join a company and doesn't have access to some of the company's data, such as billing information)
Customer (can sign up for the site and view products, add the them to cart, but can't access any of the staff or account owner features.)
All Users (no matter the role) belong to a Company and can't access another company's data. (Thus providing the the ability to run separate stores on the same app, which is needed to run this as a SAAS app.)
Account Owners and Staff can CRUD Products, but not Customers.
A great analogy would be how Shopify's admin area and customer accounts currently work for shop owners, but unlike Shopify, it doesn't require using subdomains.
Potential Models and Associations
Company
has_many :users, dependent: :destroy
has_many :products, dependent: :destroy
User
belongs_to :company
Product
belongs_to :company
Authorization
Would it work to use Pundit to restrict the controller actions based on User roles and then ensure that data is siloed off via the Model associations?
Signup Flow
I'm a little fuzzy on how to handle scoping the different User roles and where the "staff invites" and "customer" sign up could fit into a sign up flow.
Would this approach work?
Create separate controllers for "Account Owner Signup," "Staff Signup," "Customer Signup," and then embed my signup form into those views. (Using Clearance for authentication and would like to keep that if possible, but just augment it as needed).
Account Owner Signup: So if a someone signs up through the New Account Signup controller (with embedded authentication form) they would also create a Company.
Staff Invite: The Account Owner can create new Staff Users by inputing a Name and Email address. This creates a new User with the role of "Staff" (and thus cannot become Account Owners on another account). The new "Staff" user is sent an invite email that is basically password reset email inviting them to accept the invitation by creating a password.
Customer Signup: If someone signs up through the "Customer Signup" controller, they would automatically be given the user role "customer". Not sure how to set the Company ID in this case. (Pass the company_id as a hidden input on the customer sign up form?)
Is there a better way to design this type of app that I'm missing? Am I on the right track? I have no experience building something like this so any clues would be extremely helpful.
It seems like newer apps follow this type of pattern for multi-tenancy rather than subdomains.
You open with simple e-commerce site but the questions you're asking indicate that you're looking for something that's a little more complex :) You're on the right track.
The acts_as_tenant gem is worth a look. We use this now and it helps make sure your queries are all scoped appropriately.
I would also look at & evaluate rolify if you need to do roles (but don't rule out a boolean flag on your user as well).
I wouldn't rule out devise, but clearance is quite popular.
Using a subdomain might be unrealized work depending on the amount of effort, unless you need to actually use subdomains for vanity purposes (my.example.com vs example.com/my), you can do multi-tenancy without it.
I would consider separate controllers & namespacing for the different roles if their access varies wildly; you can also combine them into singular controllers using Pundit (but this could be unwieldy). You'll still want to use Pundit, however, Pundit can do things like scope the records a user should see.
You're on the right track and asking the right questions but the answers to all of these will depend on other questions (that you probably can't even answer right now).
I have a project where I'm doing what you noted (pundit to restrict data, acts_as_tenant to silo things) but as it develops certain patterns emerge that lead me down a different path. Namespacing admin, rather than doing admin checks inside the same controller for example; because if you re-write to an API you end up trying to make the same endpoint do different things and it's much cleaner to separate out the 2 endpoints behind a namespace & document the actual behavior in my opinion.

Devise Model inherit from another devise model

I'm creating an online real estate platform which facilitates (normal) Users and Real Estate agencies. This platform has following user types:
Normal Users: can search houses for rent/purchase, reviews agencies
etc.
Agencies: can upload posts for house rent/sell etc
Employee: works for a Agencies
Super Admin: manages the site
Team Member: manages portions of the site delegated by Super Admin
Each user type then has different fields (4 - 6 additional fields each). Normal Users and Agencies can self-register. Employees and Admins can be registered by Agencies and Super Admins respectively. Furthermore there are some different multiple roles for a user type (which is not relevant to discuss for this case).
Currently, I have three different devise models (User, Agencies & Super Admin). They have different login and registration forms but share same session [Reference: [https://github.com/plataformatec/devise/wiki/How-to-Setup-Multiple-Devise-User-Models]][1] User has facility to register via social media (OmniAuth) but Agency must has to register via private Email Id. And Admins will only be registered by Super Admins.
Here comes requirement [PROBLEM]:
The following fields are what I've identified as being shared between the three user types above: Email address & Username They must be unique for all cases. For example, if an agency has a username then user can't register with same username. At the moment, we are not meeting above requirement as we have different tables/models for different type of user.
MY PLAN
Now, since these are all really different users who interact with different sections of the application, so it feels clean to have three different devise models. Therefore, what I think is to create a parent devise model which should be inherited by all type of user models. This way can help me to solve my problem. But now, I'm not confident that how can I inherit parent devise model and use it for my case.
Second option is to use STI (Single Table Inheritance). But my major concern with this approach is that it will create mess with registration and security things.
Now here is my problem with my researched options. What would you recommend me to chose. Moreover, it'd be great if you put an example for elaboration.
P.s. I am inclined to the first one option as I have already done with three different models and their login/registration stuff. :)
Not sure how your going to accomplish this without a roles system.. I suppose you could namespace or scope it out but wouldnt be the most secure to keep access to a specific user group.. atleast in my oppinion.
You could Use Single Table Inheritance (STI)..
Essentially you could do something like this (if i understand you correctly)
class User < ApplicationRecord
# MASTER
end
class AnotherUser < User
# Inherits attributes from User Table
end
going this route you will need to add type:string to your User model.
here are a few reference links:
http://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html
http://eewang.github.io/blog/2013/03/12/how-and-when-to-use-single-table-inheritance-in-rails/
they helped me when i ventured down the STI path..
For example, In a project I did this like so:
class User < ApplicationRecord
# Devise Modules Here
end
class Admin < User
end
class SuperAdmin < User
end
now the tricky part I found was creating these users.. i opted to create a controller and a link, so in my app a User is created only by an admin or superadmin, and an admin can only be created by a superadmin

Multiple devise models vs permission based

The application I'm building is to allow users to download vouchers. Employers sign up for an account, add employees (who get a login), select which vouchers to enable, and then employees can see the enabled vouchers and download.
I initially created two devise models: employee and employer. This was so active record associations would be simple (employer has_many employees, employee has_many vouchers). But this would also mean separate database tables and therefore separate sign in forms.
I looked into single sign in forms for multiple users and this seemed to have the consensus that you should instead have a single User model and use CanCan and Rolify for permissions. But the problem with that is you cannot (I believe?) do active record associations between these two roles (not separate models).
I next looked at subclassing so I could do associations, but it has issues as people say Rails isn't really meant to subclass, and it seems a bit hacky.
So I'm left feeling like I have to choose the lesser of evils, whereas I really just want to find the right way.. Thanks in advance for any help.
So a friend of mine solved this very elegantly for me, for everyones reference:
Good question. It’s a great problem that deals with the intersection between good engineering (model implementation, database design) and user experience (single sign in form).
Assuming that Employees and Employers differ enough, it makes sense to implement them as separate models. But it also makes sense to have a single sign in form—employees and employers shouldn’t have to care that they’re signing into the right form.
Single table inheritence usually appears to be the ideal solution, but tends to best be avoided in Ruby on Rails applications unless absolutely necessary.
I’ve actually thought about this problem before, so I would suggest an implementation along these lines:
An Employer model.
An Employee model.
A SignIn/Login/Credentials/WhateverYouWantToCallIt model.
In terms of employer/employee associations, as before:
Employee belongs_to :employer
Employer has_many :employees
Now, considering that both models are able to sign in, it makes sense to separate these credentials into their own SignIn model. If you do some reading up on polymorphic associations (http://guides.rubyonrails.org/association_basics.html#polymorphic-associations), you’ll find that they are awesome for creating relationships where the association can be with different models.
So now you need to create associations between sign in credentials and employers and employees:
SignIn :belongs_to :signinable, polymorphic: true
Employer has_one :sign_in, as: :signinable
Employee has_one :sign_in, as: :signinable
The elegance of this solution (in my opinion), is that you’re able to separate your SignIn, Employer and Employee models, which not only conforms to good Ruby on Rails conventions, but is good database normalisation practice. At the same time, you have a SignIn model that makes it trivial to implement a better sign in form experience that allows both employers and employees to sign in.

Rails Single table inheritance (STI) with the User model

Building a small reservation site. There are users (who login and work with the site) and there are guests who are being assigned to rooms. users can be (are?) guests but not all guests are also users.
My initial inclination was to set up a belongs_to / has_one relationship between user & guest but maybe STI would work here? Being as users & guests have first_name, last_name, email etc. does it make sense to set up the model such that, say, user and guest both inherit from person?
I will roll my own simplistic authentication so the only additional fields user is likely to have are password_digest, roles_mask and a icon_color.
Any suggestions? I only ask because things can get tricky around authentication, authorization & whatnot.
Appreciate any ideas/tips!
The simplest approach here would be to, as suggested, stick to STI. You can, for example, setup a single devise User model as well as apply ACL with CanCan and define roles for your users.
CanCan's ability spec will determine which resources are accessible and what are not. The advantage here is that users can be guests, and depending on how you setup your ACL, guests can be prevented from having admin like access.
However, Jesse's suggestion of going two separate Devise models is also a good idea as this ensures their sessions are separate. This is more straightforward to implement as you can then setup a User-specific ACL and Guest-specific ACL accordingly.
https://github.com/ryanb/cancan

Roles that are User<>Project Based

Currently I'm using Devise & CanCan which allows me to create
Users with Roles using a (Roles_Users) table.
That's nice, but what I want is to Have Projects in my app and for
each project for a user to possibly have a role like (Admin, Viewer,
etc) IE, roles are not assigned to users but to users based on what projects they are a member of.
Examples:
User X belong to Project A with an Admin Role
User X belong to Project B with an Guest Role
User Y belong to Project B with an Observer Role
What kind of Model would work for this?
Models
Users
has_many: projects
Projects
?
Roles
?
Users_Roles_Projects (user_id, project_id, role_id)
What do you think? I'm a newbie and could use the understanding and
thinking from you fine experienced folks. Thanks!
You should have a look in to has_many :through. This Railscast should get you up and running: http://railscasts.com/episodes/47-two-many-to-many
For example, you could have User has_many Projects through Memberships (I'm sure you can come up with a better name!)
Your Users model would contain the standard user details, the Projects model would contain the project details and presumably you have some Roles model somewhere (I've not used either of the libraries you mentioned so I can't comment in terms of how they work). The key is the Memberships model.
The membership model would contain the userID, projectID and a roleID. In the database there should only be one instance of any given userID and projectID pairing so by storing the roleID along side this pairing you can assign the role to that user on the specified project.

Resources