Moneygun - Add ActsAsTenant to an existing application | SupeRails #209

30/10/2024
moneygun Mulitenancy saas saas boilerplate
  • Multitenancy basics
  • Install ActsAsTenant
  • Enforce Require tenant

Enhance Multi-Tenancy with ActsAsTenant in Rails!

In this episode, we’re diving into ActsAsTenant—a gem that strengthens your multi-tenancy setup by ensuring that each tenant can only access their own data.

While ActsAsTenant doesn't provide the database architecture for multi-tenancy, it adds an essential layer of security and data isolation to your Rails app.

In the previous episode, I introduced Moneygun, a multi-tenancy boilerplate for Rails.

Now we’ll take it a step further by integrating ActsAsTenant into our existing setup, enhancing security and data management.

0:00 Multitenancy basics
1:50 Install ActsAsTenant
5:30 Enforce Require tenant

Pull Request adding ActsAsTenant https://github.com/yshmarov/moneygun/pull/120

Gem ActsAsTenant: https://github.com/ErwinM/acts_as_tenant

Transcript
Hello friends. So in the previous episode, I have introduced Mon Gun, uh, multi-tenancy boilerplate for Ruby Rails, where basically you can have, uh, organizations and users can be members of an organization and they can, uh, have role-based access to different resources inside the organization. It is built more or less on this kind of architecture. Um, here's an example of, uh, a user that can have multiple memberships within an, uh, account organization or this is basically the, uh, architecture where everything belongs to a tenant organization or account, whatever you name it. And, uh, all the downstream resources should have, uh, a tenant or organization ID set. So everything is scoped to an organization. Now, whenever you want to implement multitenancy, you would maybe think of adding some kind of multitenancy gem that, uh, would, uh, have to solve all your multitenancy problems. But, uh, usually this kind of multitenancy gems, they don't, uh, provide the whole solution to, uh, like the views and the controllers. They only provide some kind of, uh, mechanism to scope, uh, the data from the database. So for example, GEM acts as sta it's main feature is being an additional layer of security. So, uh, if you add the gem access talent, you can, uh, add the, a default scope to your projects, for example, that you cannot, uh, query all the projects in the database. You have the first set and, uh, tenant or, and organization. You can only get projects within one organization. So this kind of an additional security measure. Now let's try adding the gem external to an existing application. In this case, I'm going to edit to the default code provided by Mon Gun and let's see how we can make external work. So first of all, I'm going to go and add the gem access tenant to my gem file. Uh, yeah, I'll add it next to authentication authorization bundle. So going further, I need to set the current tenant for the access tenant gem. So I'm not doing it by based on sub domain or based on domain. We have, uh, uh, URL or root based, uh, uh, way of setting the current, uh, organization. So I'm going to set the current tenant through a filter. I'm going to copy this and, uh, where I'm going, am I going to set the current tenant? Now? Definitely not on application controller because I want to have resources in my application that, uh, don't require current tenant. I can set it somewhere on organization level. So all the resources that are scoped to organization will, uh, have to have, uh, this in their controllers. Uh, they are going to set the tenant, uh, uh, inside the resources that belong to a tenant or organization. Now you see in inboxes, they inherit from organization space controller, same with memberships. They inherit from organizations base controller. And in the space controller, I have this, uh, before action tool set their organization. So instead of this before action, I'm going to copy the code from access tenant set, current tenant through filter. Uh, the before action will, let's name it, uh, set tenant or it can actually be the set organization. And uh, we'll have, uh, current organization, uh, equals, um, equals, uh, actually it's going to equal uh, this current user organizations signed by Pars organization. So yeah, we have uh, have as in current account, we certain current tenant for excess tenant and uh, we might still want to say at the organization equals current account. Okay. Uh, we can comment out the previous way of how we were setting the current organization. Now we also set the, the organization on exist, tenant level. Let's, uh, try run our tests and see if the tests are possible. Okay. And define method set organization. Yeah, I have to, uh, advise it undefined because I didn't rename this one. Okay, let's run the tests once again. Okay, so all the tests, uh, are passing looks, uh, fine, but, uh, we really didn't, uh, do anything yet. Uh, we need to still say that, uh, each model that, uh, has to be scoped on organization, uh, belongs to it via Access standard. So, uh, going further, we have the example of scoping the models. They're going to say instead of belongs to organization, they're going to say acts as tenant organization. So, uh, which ones artists, these are membership. RB instead of belongs to organization, we'll have as tenant organization. It's kind of belongs to plus a scope. And uh, same with inboxes. An example of, uh, message resource instead of belongs to organization will have extended organization. Let's run the test once again and see if, uh, they're still poss yes, they are still poss. Now let's try getting all the, uh, inboxes in our application inbox. Do all you see, we can get the inboxes across multiple organizations at the moment from our console and we could in theory say inbox destroy all and it'll destroy all the inboxes. And it is, uh, not considered a good security practice to be able to, uh, get all resources from all the organizations, uh, programmatically or from kind of some kind of super user. You don't, you shouldn't have access to resources outside of your organization. So, uh, that's why we are going down to the main security feature of X as tenant. That is, uh, the setting to require a tenant. Let's create this X as tenant initializer. Uh, if you are adding the gem tenant, I think there's uh, no point of adding the this gem if you don't, uh, uh, add the required tenant as true. So let's say require tenant, let's run the tests. Okay? And now that tests are failing with the no tenant set. Interesting. Now before fixing the test, let's have a look at our rails console. So again, I will try to get all the inboxes inbox. Oh, and you see I get this error access tenant, no tenant set. And this is what, uh, this, uh, config required tenant does. So I cannot get all the tenants or all the inboxes. I have to have a tenant set. Let's try sending the tenant in the console acts, uh, as tenant current tenant equals organization dot first. Now let's get all the inboxes. And you see, inbox all gets only the inboxes that belong to this organization. So you see this is an additional level of security, a default scope, having to set the organization to be able to get resources scope to the organization. This is, uh, a good security feature. Okay, so let's go and, uh, make our application work with, uh, this, uh, setting of required tenant equals true. First of all, we see that this, uh, test is failing, uh, organization's controller, uh, no tenants set. So we've got all the tests kind of failing at the moment. Now let's try run our server and uh, try to see something. So you see, I just tried to go to the root path and current user organizations fails because we need to have the tenant set. Why do we need to have the tenant set to just view the organizations where the current user is a member? Well, because we set exist tenant on the membership. So actually you don't want to set exist tenant on the membership of current user. You want to set it on all the other downstream models except of membership. So membership is just going to be belongs to memberships should be accessible without having, uh, like the list of memberships of the current user should be accessible without having the current tenant set. So, uh, okay, now launching the app, folks, I can navigate around. Let's try running the tests again. Okay. And we have just three tests, uh, failing. So, uh, uh, Uh, one is in organizations controller and two in inboxes controller. So organizations controller test, uh, 64. Let's have a look at this. Test line 64 is for on admin can destroy an organization. Let's go to our organization's, uh, controller here for Destroy. Now for the destroy action, we need to actually set the current tenant. Why? Because, uh, it is going to affect the downstream, uh, uh, association. So, uh, to be able to destroy, like organization has many inboxes. To be able to destroy the inboxes of an organization, we need to set the current organization. So here we're going to manually set the, the tenant, uh, I will say acts, uh, as tenant with tenant at organization, do, uh, organization destroy, and this way the downstream, uh, inboxes will be destroyed. Let's run the test once again. And we have just, uh, two tests failing inside our inboxes controller. Test line 23. So you see this so difference is failing. And actually it's an interesting case that we have to manually set the tenant around this request. I found the solution here in this, uh, thread that, uh, uh, tenant doesn't work well with this assert difference. So we need to manually set the, the tenant. I think we should say something like, uh, x as tenant, uh, dot current tenant equals organization here and here. Let's see if this test passes. Okay, we have just, uh, mm yeah, it's still not possible. Maybe I need to set it also before, okay, we have just one failure left. Let's, uh, fix that destroying organization with a certain difference. So one, two, setting the tenant. Okay? And voila, our application has integrated the as tenant with, uh, strict, uh, security. So access tenant has to be always set to access, uh, uh, resources that are scoped to an organization and, uh, all out as person. Everything works. So that's how you integrate, act as tenant with a strict security into an existing application. Thanks for watching and see you in the next one.
0
Join the conversation
Sign in to access PRO lessons, access private repos, leave comments, create watch lists.
We collect your email address, name and username to create your account. We do not share your email address with anyone else.