Giter VIP home page Giter VIP logo

multi-tenant-loopback-example-two's Introduction

multi-tenant-loopback-example-two

Deploy

Key Differences

The key difference from other examples: Organization has User as its base model

What does multi-tenant mean to us?

  1. Users can belong to an organization.
  2. We can have multiple organizations in the same loopback server/db.
  3. Any data created for the organization or any data created by the users of that organization should be segragated from the the users of another organization.
  4. If we want, we should be able to configure the users that belong to the same organization to view any data that other users of that organization created.

Assumptions

  1. A sysAdmin like role for access over REST is unnecessary when compared to the security threat it poses. One can always SSH into the server directl and use something like loopback-console instead for sysAdmin purposes. Possibly exposing some kind of server-side-only impersonation API would make it even more useful.
  2. Currently there aren't any known use cases which merit exposing TeamModel over REST and given how it would complicate the ACLs a lot when attempting to prevent REST~ful users from adding themselves to other teams ... its just better to keep TeamModel at server-side-only.

Improvements

  • Instead of saving role as-is into TeamModel when provided by a restful client, some sort of validation against pre-seeded Roles should be performed.

Testing Multi Tenancy

  • Remove any pre-existing db.json file and run the server with npm start
#0.a setup HOST_URL

#    > when running locally
export HOST_URL=http://localhost:3000

#    > when running agaisnt heroku or something like that
export HOST_URL=https://multi-tenant-loopback-example-two.herokuapp.com

#0.b make sure that HOST_URL is setup
echo $HOST_URL

#1.1 orgAdminA logs in
export ORG_ADMIN_A_TOKEN=`curl -X POST \
  "$HOST_URL/api/UserModels/login" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  -d "{\"username\":\"[email protected]\", \"password\":\"orgAdminA\"}" | \
  node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).id"`

#1.2 make sure that ORG_ADMIN_A_TOKEN is setup
echo $ORG_ADMIN_A_TOKEN

#2.1 orgAdminB logs in
export ORG_ADMIN_B_TOKEN=`curl -X POST \
  "$HOST_URL/api/UserModels/login" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  -d "{\"username\":\"[email protected]\", \"password\":\"orgAdminB\"}" | \
  node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).id"`

#2.2 make sure that ORG_ADMIN_B_TOKEN is setup
echo $ORG_ADMIN_B_TOKEN

#3 orgAdminA creates stuff
curl -w "\n" \
  -X POST \
  "$HOST_URL/api/StuffModels?access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  -d "{\"name\": \"stuff for orgA\"}"

#4 orgAdminB creates stuff
curl -w "\n" \
  -X POST \
  "$HOST_URL/api/StuffModels?access_token=$ORG_ADMIN_B_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  -d "{\"name\": \"stuff for orgB\"}"

#5 orgAdminA can get stuff which is specific to orgA
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels/1?access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Content-Type: application/json"

#6 orgAdminA can NOT get stuff from another org
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels/2?access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Content-Type: application/json"

#7 orgAdminA can only LIST stuff which is specific to orgA
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels?access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Accept: application/json"

#8 orgAdminB can only LIST stuff which is specific to orgB
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels?access_token=$ORG_ADMIN_B_TOKEN" \
  --header "Accept: application/json"

#9 orgAdminA can only FIND stuff which is specific to orgA
#  filter={"where":{"name":{"like":"stuff"}}}
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels?filter=%7B%22where%22%3A%7B%22name%22%3A%7B%22like%22%3A%22stuff%22%7D%7D%7D&access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Accept: application/json"

#10 orgAdminA can only FIND-ONE stuff which is specific to orgA
#   filter={"where":{"name":{"like":"stuff for orgB"}}}
#   SHOULD return 404 with MODEL_NOT_FOUND
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels/findOne?filter=%7B%22where%22%3A%7B%22name%22%3A%7B%22like%22%3A%22stuff%20for%20orgB%22%7D%7D%7D&access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Accept: application/json"

#11 orgAdminA can only FIND-BY-ID stuff which is specific to orgA
#   SHOULD return 404 with MODEL_NOT_FOUND
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels/2?access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Accept: application/json"

#12 orgAdminA can create other users like a storeAdmin
curl -w "\n" \
  -X POST \
  "$HOST_URL/api/UserModels?access_token=$ORG_ADMIN_A_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  -d '{"seedWithRole": "storeAdmin", "seedWithOrg":"orgA", "username": "[email protected]", "email": "[email protected]", "password": "storeAdminA3"}'

#13.1 storeAdminA3 logs in
export STORE_ADMIN_A3_TOKEN=`curl -X POST \
  "$HOST_URL/api/UserModels/login" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  -d "{\"username\":\"[email protected]\", \"password\":\"storeAdminA3\"}" | \
  node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).id"`

#13.2 storeAdminA3 cannot create other users, this request should fail
curl -w "\n" \
  -X POST \
  "$HOST_URL/api/UserModels?access_token=$STORE_ADMIN_A3_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  -d '{"seedWithRole": "storeAdmin", "seedWithOrg":"orgA", "username": "[email protected]", "email": "[email protected]", "password": "storeAdminA4"}'

#13.2 storeAdminA3 can get stuff which is specific to orgA
curl -w "\n" \
  -X GET \
  "$HOST_URL/api/StuffModels?access_token=$STORE_ADMIN_A3_TOKEN" \
  --header "Accept: application/json"

High-Level Implementation Details

  1. A user signup should always create a new organization and new team entry to track the user as the organization's administrator (orgAdmin).
  2. The create method for UserModel is overridden so that the server-side is responsible for creating an OrgModel and doesn't trust the client to do so when a user signs up.
  3. The after save hook on UserModel is used to tie together a newly created user with an organization and a role by creating a new entry in TeamModel.

Test Heroku deployment

Attributions

The project is generated by LoopBack.

multi-tenant-loopback-example-two's People

Contributors

pulkitsinghal avatar

Stargazers

Caio Villela avatar

Watchers

 avatar James Cloos avatar  avatar Harshad Yeola avatar

multi-tenant-loopback-example-two's Issues

Add test to demo that the password field has been removed

loopback-testing module fills reasonable defaults for missing fields when using TestDataBuilder ... so use it to assert that a new OrgModel doesn't have any password field auto populated for it.

// something like this would result in a password field being auto-populated in the DB
// if it wasn't omitted ... so the test should assert that no `password` field exists afterwards
new lt.TestDataBuilder()
      .define('orgModel', app.datasources.mongodb.models.OrgModel, {name: 'test'})
      .buildTo(this, done);
...

  it('This is a test.', function (done) {
    var orgModel = this.orgModel;
    expect(orgModel.password).to.not.exist;
    expect(orgModel.name).to.exist;
    done();
  });

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.