├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .yo-rc.json ├── README.md ├── client └── README.md ├── common └── models │ ├── content.js │ ├── content.json │ ├── customer.js │ ├── customer.json │ ├── multi-access-token.js │ ├── multi-access-token.json │ ├── vendor.js │ └── vendor.json ├── package.json └── server ├── boot ├── authentication.js ├── root.js ├── sample-data.js └── sample-data.json ├── component-config.json ├── config.json ├── datasources.json ├── middleware.development.json ├── middleware.json ├── model-config.json └── server.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /client/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "loopback" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.iml 4 | *.log 5 | *.out 6 | *.pid 7 | *.seed 8 | *.sublime-* 9 | *.swo 10 | *.swp 11 | *.tgz 12 | *.xml 13 | .DS_Store 14 | .idea 15 | .project 16 | .strong-pm 17 | coverage 18 | node_modules 19 | npm-debug.log 20 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-loopback": {} 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loopback-example-multiple-user-models 2 | 3 | ## This example project is no longer maintained. 4 | 5 | ### For a more up-to-date example of how to use LoopBack 3 with multi-user roles please check [codekeyz/loopback-multirole-api-starter](https://github.com/codekeyz/loopback-multirole-api-starter). 6 | 7 | 8 | 9 | > Example project to show a new feature in LoopBack 3.3.0: Access Control with multiple user models 10 | 11 | 12 | Read more about this feature here: http://loopback.io/doc/en/lb3/Authentication-authorization-and-permissions.html#access-control-with-multiple-user-models 13 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | ## Client 2 | 3 | This is the place for your application front-end files. 4 | -------------------------------------------------------------------------------- /common/models/content.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Content) { 4 | 5 | /** 6 | * Get the content for the customers 7 | */ 8 | Content.customer = () => Promise.resolve('This is the Customer Content'); 9 | 10 | /** 11 | * Get the content for the vendors 12 | */ 13 | Content.vendor = () => Promise.resolve('This is the Vendor Content'); 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /common/models/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Content", 3 | "base": "Model", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": {}, 9 | "validations": [], 10 | "relations": {}, 11 | "acls": [ 12 | { 13 | "accessType": "*", 14 | "principalType": "ROLE", 15 | "principalId": "$everyone", 16 | "permission": "DENY" 17 | }, 18 | { 19 | "accessType": "*", 20 | "principalType": "Customer", 21 | "principalId": "$everyone", 22 | "permission": "ALLOW", 23 | "property": "customer" 24 | }, 25 | { 26 | "accessType": "*", 27 | "principalType": "Vendor", 28 | "principalId": "$everyone", 29 | "permission": "ALLOW", 30 | "property": "vendor" 31 | } 32 | ], 33 | "methods": { 34 | "customer": { 35 | "accepts": [], 36 | "returns": { 37 | "arg": "result", 38 | "type": "string", 39 | "root": true 40 | }, 41 | "description": "Get the content for the customers", 42 | "http": { 43 | "path": "/customer", 44 | "verb": "GET" 45 | } 46 | }, 47 | "vendor": { 48 | "accepts": [], 49 | "returns": { 50 | "arg": "result", 51 | "type": "string", 52 | "root": true 53 | }, 54 | "description": "Get the content for the vendors", 55 | "http": { 56 | "path": "/vendor", 57 | "verb": "GET" 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /common/models/customer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Customer) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /common/models/customer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Customer", 3 | "base": "User", 4 | "idInjection": false, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "id": { 10 | "type": "number", 11 | "id": true, 12 | "required": true 13 | } 14 | }, 15 | "validations": [], 16 | "relations": { 17 | "accessTokens": { 18 | "type": "hasMany", 19 | "model": "MultiAccessToken", 20 | "polymorphic": { 21 | "foreignKey": "userId", 22 | "discriminator": "principalType" 23 | }, 24 | "options": { 25 | "disableInclude": true 26 | } 27 | } 28 | }, 29 | "acls": [], 30 | "methods": {} 31 | } 32 | -------------------------------------------------------------------------------- /common/models/multi-access-token.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Multiaccesstoken) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /common/models/multi-access-token.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MultiAccessToken", 3 | "base": "AccessToken", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": {}, 9 | "validations": [], 10 | "relations": { 11 | "user": { 12 | "type": "belongsTo", 13 | "idName": "id", 14 | "polymorphic": { 15 | "idType": "string", 16 | "foreignKey": "userId", 17 | "discriminator": "principalType" 18 | } 19 | } 20 | }, 21 | "acls": [], 22 | "methods": {} 23 | } 24 | -------------------------------------------------------------------------------- /common/models/vendor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Vendor) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /common/models/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vendor", 3 | "base": "User", 4 | "idInjection": false, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "id": { 10 | "type": "number", 11 | "id": true, 12 | "required": true 13 | } 14 | }, 15 | "validations": [], 16 | "relations": { 17 | "accessTokens": { 18 | "type": "hasMany", 19 | "model": "MultiAccessToken", 20 | "polymorphic": { 21 | "foreignKey": "userId", 22 | "discriminator": "principalType" 23 | }, 24 | "options": { 25 | "disableInclude": true 26 | } 27 | } 28 | }, 29 | "acls": [], 30 | "methods": {} 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-example-multiple-user-models", 3 | "version": "1.0.0", 4 | "main": "server/server.js", 5 | "scripts": { 6 | "lint": "eslint .", 7 | "start": "node .", 8 | "posttest": "npm run lint && nsp check" 9 | }, 10 | "dependencies": { 11 | "compression": "^1.0.3", 12 | "cors": "^2.5.2", 13 | "helmet": "^1.3.0", 14 | "loopback-boot": "^2.6.5", 15 | "serve-favicon": "^2.0.1", 16 | "strong-error-handler": "^1.0.1", 17 | "loopback-component-explorer": "^4.0.0", 18 | "loopback": "^3.0.0" 19 | }, 20 | "devDependencies": { 21 | "eslint": "^2.13.1", 22 | "eslint-config-loopback": "^4.0.0", 23 | "nsp": "^2.1.0" 24 | }, 25 | "repository": { 26 | "type": "", 27 | "url": "" 28 | }, 29 | "license": "UNLICENSED", 30 | "description": "loopback-example-multiple-user-models" 31 | } 32 | -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function enableAuthentication(server) { 4 | // enable authentication 5 | server.enableAuth(); 6 | }; 7 | -------------------------------------------------------------------------------- /server/boot/root.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(server) { 4 | // Install a `/` route that returns server status 5 | var router = server.loopback.Router(); 6 | router.get('/', server.loopback.status()); 7 | server.use(router); 8 | }; 9 | -------------------------------------------------------------------------------- /server/boot/sample-data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const sampleData = require('./sample-data.json'); 4 | 5 | module.exports = function (app, cb) { 6 | 7 | const promises = []; 8 | 9 | Object.keys(sampleData).forEach(modelName => { 10 | const Model = app.models[modelName]; 11 | const modelItems = sampleData[modelName]; 12 | 13 | modelItems.forEach(modelItem => { 14 | promises.push(new Promise(resolve => { 15 | Model.upsert(modelItem).then(resolve) 16 | })) 17 | }) 18 | }); 19 | 20 | 21 | return Promise 22 | .all(promises) 23 | .then((res) => { 24 | console.log('Created %s items', res.length); 25 | return cb() 26 | }) 27 | .catch(cb); 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /server/boot/sample-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "Customer": [ 3 | {"id": "1", "username": "customer1@example.com", "email": "customer1@example.com", "password": "password"}, 4 | {"id": "2", "username": "customer2@example.com", "email": "customer2@example.com", "password": "password"}, 5 | {"id": "3", "username": "customer3@example.com", "email": "customer3@example.com", "password": "password"} 6 | ], 7 | "Vendor": [ 8 | {"id": "1", "username": "vendor1@example.com", "email": "vendor1@example.com", "password": "password"}, 9 | {"id": "2", "username": "vendor2@example.com", "email": "vendor2@example.com", "password": "password"}, 10 | {"id": "3", "username": "vendor3@example.com", "email": "vendor3@example.com", "password": "password"} 11 | ], 12 | "MultiAccessToken": [ 13 | {"id":"customer-token-1","ttl":1209600, "userId":1,"principalType":"Customer"}, 14 | {"id":"customer-token-2","ttl":1209600, "userId":2,"principalType":"Customer"}, 15 | {"id":"customer-token-3","ttl":1209600, "userId":3,"principalType":"Customer"}, 16 | {"id":"vendor-token-1","ttl":1209600, "userId":1,"principalType":"Vendor"}, 17 | {"id":"vendor-token-2","ttl":1209600, "userId":2,"principalType":"Vendor"}, 18 | {"id":"vendor-token-3","ttl":1209600, "userId":3,"principalType":"Vendor"} 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/api", 3 | "host": "0.0.0.0", 4 | "port": 3000, 5 | "remoting": { 6 | "context": false, 7 | "rest": { 8 | "handleErrors": false, 9 | "normalizeHttpPath": false, 10 | "xml": false 11 | }, 12 | "json": { 13 | "strict": false, 14 | "limit": "100kb" 15 | }, 16 | "urlencoded": { 17 | "extended": true, 18 | "limit": "100kb" 19 | }, 20 | "cors": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "name": "db", 4 | "connector": "memory", 5 | "file": "/tmp/db.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /server/middleware.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "final:after": { 3 | "strong-error-handler": { 4 | "params": { 5 | "debug": true, 6 | "log": true 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | }, 14 | "helmet#xssFilter": {}, 15 | "helmet#frameguard": { 16 | "params": [ 17 | "deny" 18 | ] 19 | }, 20 | "helmet#hsts": { 21 | "params": { 22 | "maxAge": 0, 23 | "includeSubdomains": true 24 | } 25 | }, 26 | "helmet#hidePoweredBy": {}, 27 | "helmet#ieNoOpen": {}, 28 | "helmet#noSniff": {}, 29 | "helmet#noCache": { 30 | "enabled": false 31 | } 32 | }, 33 | "session": {}, 34 | "auth": {}, 35 | "parse": {}, 36 | "routes": { 37 | "loopback#rest": { 38 | "paths": [ 39 | "${restApiRoot}" 40 | ] 41 | } 42 | }, 43 | "files": {}, 44 | "final": { 45 | "loopback#urlNotFound": {} 46 | }, 47 | "final:after": { 48 | "strong-error-handler": {} 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ], 9 | "mixins": [ 10 | "loopback/common/mixins", 11 | "loopback/server/mixins", 12 | "../common/mixins", 13 | "./mixins" 14 | ] 15 | }, 16 | "User": { 17 | "dataSource": "db", 18 | "public": false 19 | }, 20 | "AccessToken": { 21 | "dataSource": "db", 22 | "public": false 23 | }, 24 | "ACL": { 25 | "dataSource": "db", 26 | "public": false 27 | }, 28 | "RoleMapping": { 29 | "dataSource": "db", 30 | "public": false, 31 | "options": { 32 | "strictObjectIDCoercion": true 33 | } 34 | }, 35 | "Role": { 36 | "dataSource": "db", 37 | "public": false 38 | }, 39 | "Customer": { 40 | "dataSource": "db", 41 | "public": true 42 | }, 43 | "Vendor": { 44 | "dataSource": "db", 45 | "public": true 46 | }, 47 | "MultiAccessToken": { 48 | "dataSource": "db", 49 | "public": false 50 | }, 51 | "Content": { 52 | "dataSource": null, 53 | "public": true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var loopback = require('loopback'); 4 | var boot = require('loopback-boot'); 5 | 6 | var app = module.exports = loopback(); 7 | 8 | app.use(loopback.token({ 9 | model: app.models.MultiAccessToken 10 | })); 11 | 12 | app.start = function() { 13 | // start the web server 14 | return app.listen(function() { 15 | app.emit('started'); 16 | var baseUrl = app.get('url').replace(/\/$/, ''); 17 | console.log('Web server listening at: %s', baseUrl); 18 | if (app.get('loopback-component-explorer')) { 19 | var explorerPath = app.get('loopback-component-explorer').mountPath; 20 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 21 | } 22 | }); 23 | }; 24 | 25 | // Bootstrap the application, configure models, datasources and middleware. 26 | // Sub-apps like REST API are mounted via boot scripts. 27 | boot(app, __dirname, function(err) { 28 | if (err) throw err; 29 | 30 | // start the server if `$ node server.js` 31 | if (require.main === module) 32 | app.start(); 33 | }); 34 | --------------------------------------------------------------------------------