diff --git a/examples/url-scheme/.editorconfig b/examples/credentials/.editorconfig similarity index 100% rename from examples/url-scheme/.editorconfig rename to examples/credentials/.editorconfig diff --git a/examples/credentials/.eslintignore b/examples/credentials/.eslintignore new file mode 100644 index 0000000..44f3970 --- /dev/null +++ b/examples/credentials/.eslintignore @@ -0,0 +1 @@ +/client/ \ No newline at end of file diff --git a/examples/credentials/.eslintrc b/examples/credentials/.eslintrc new file mode 100644 index 0000000..a6e5297 --- /dev/null +++ b/examples/credentials/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "loopback" +} \ No newline at end of file diff --git a/examples/url-scheme/.gitignore b/examples/credentials/.gitignore similarity index 100% rename from examples/url-scheme/.gitignore rename to examples/credentials/.gitignore diff --git a/examples/url-scheme/.yo-rc.json b/examples/credentials/.yo-rc.json similarity index 100% rename from examples/url-scheme/.yo-rc.json rename to examples/credentials/.yo-rc.json diff --git a/examples/credentials/README.md b/examples/credentials/README.md index 323bfd0..f946bcf 100644 --- a/examples/credentials/README.md +++ b/examples/credentials/README.md @@ -1,6 +1,19 @@ ->WORK IN PROGRESS +# Credentials (header) scheme -# Access token or credentials +Credential schemes provide tenant information as credentials or as part of the +access token (ie. in the HTTP headers or query parameters). -An example that provides tenant information as part of the access token or -credentials (ie. in the HTTP headers or query parameters). +In this example, we provide the credentials using basic access authentication +(AKA basic auth). + +## Usage + +```shell +npm start + +curl -u Joe:doe localhost:3000/api/Joe/Todos +# returns [{"content":"a","id":1},{"content":"b","id":2},{"content":"c","id":3}] + +curl -u Bob:doe localhost:3000/api/Bob/Todos +# returns [{"content":"d","id":1},{"content":"e","id":2},{"content":"f","id":3}] +``` diff --git a/examples/url-scheme/client/README.md b/examples/credentials/client/README.md similarity index 100% rename from examples/url-scheme/client/README.md rename to examples/credentials/client/README.md diff --git a/examples/credentials/common/models/one-joe-todo.js b/examples/credentials/common/models/one-joe-todo.js new file mode 100644 index 0000000..c77964d --- /dev/null +++ b/examples/credentials/common/models/one-joe-todo.js @@ -0,0 +1,3 @@ +module.exports = function(Onejoetodo) { + +}; diff --git a/examples/url-scheme/common/models/one-joe-todo.json b/examples/credentials/common/models/one-joe-todo.json similarity index 100% rename from examples/url-scheme/common/models/one-joe-todo.json rename to examples/credentials/common/models/one-joe-todo.json diff --git a/examples/credentials/common/models/two-bob-todo.js b/examples/credentials/common/models/two-bob-todo.js new file mode 100644 index 0000000..45a1b78 --- /dev/null +++ b/examples/credentials/common/models/two-bob-todo.js @@ -0,0 +1,3 @@ +module.exports = function(Twobobtodo) { + +}; diff --git a/examples/url-scheme/common/models/two-bob-todo.json b/examples/credentials/common/models/two-bob-todo.json similarity index 100% rename from examples/url-scheme/common/models/two-bob-todo.json rename to examples/credentials/common/models/two-bob-todo.json diff --git a/examples/credentials/common/models/user.js b/examples/credentials/common/models/user.js new file mode 100644 index 0000000..93b7c6b --- /dev/null +++ b/examples/credentials/common/models/user.js @@ -0,0 +1,3 @@ +module.exports = function(User) { + +}; diff --git a/examples/credentials/common/models/user.json b/examples/credentials/common/models/user.json new file mode 100644 index 0000000..652802f --- /dev/null +++ b/examples/credentials/common/models/user.json @@ -0,0 +1,20 @@ +{ + "name": "user", + "base": "PersistedModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/examples/credentials/package.json b/examples/credentials/package.json new file mode 100644 index 0000000..c99dea2 --- /dev/null +++ b/examples/credentials/package.json @@ -0,0 +1,32 @@ +{ + "name": "credentials", + "version": "1.0.0", + "main": "server/server.js", + "scripts": { + "start": "node .", + "pretest": "eslint .", + "posttest": "nsp check" + }, + "dependencies": { + "basic-auth": "^1.0.4", + "compression": "^1.0.3", + "cors": "^2.5.2", + "helmet": "^1.3.0", + "loopback": "^2.22.0", + "loopback-boot": "^2.6.5", + "loopback-component-explorer": "^2.4.0", + "loopback-datasource-juggler": "^2.39.0", + "loopback-multitenancy": "git+ssh://git@github.com/strongloop/loopback-multitenancy.git", + "serve-favicon": "^2.0.1" + }, + "devDependencies": { + "eslint": "^2.5.3", + "nsp": "^2.1.0" + }, + "repository": { + "type": "", + "url": "" + }, + "license": "UNLICENSED", + "description": "credentials" +} diff --git a/examples/credentials/server/boot/create-bob-sample-data.js b/examples/credentials/server/boot/create-bob-sample-data.js new file mode 100644 index 0000000..3fc5328 --- /dev/null +++ b/examples/credentials/server/boot/create-bob-sample-data.js @@ -0,0 +1,12 @@ +module.exports = function(app, cb) { + app.models.Two_Bob_Todo.create([ + {content: 'd'}, + {content: 'e'}, + {content: 'f'}, + ], function(err, models) { + if (err) throw err; + + console.log('created bob models:', models); + cb(); + }); +}; diff --git a/examples/credentials/server/boot/create-joe-sample-data.js b/examples/credentials/server/boot/create-joe-sample-data.js new file mode 100644 index 0000000..5a9bf6c --- /dev/null +++ b/examples/credentials/server/boot/create-joe-sample-data.js @@ -0,0 +1,12 @@ +module.exports = function(app, cb) { + app.models.One_Joe_Todo.create([ + {content: 'a'}, + {content: 'b'}, + {content: 'c'}, + ], function(err, models) { + if (err) throw err; + + console.log('created joe models:', models); + cb(); + }); +}; diff --git a/examples/credentials/server/boot/create-users.js b/examples/credentials/server/boot/create-users.js new file mode 100644 index 0000000..ff502e3 --- /dev/null +++ b/examples/credentials/server/boot/create-users.js @@ -0,0 +1,11 @@ +module.exports = function(app, cb) { + app.models.user.create([ + {username: 'Joe', password: 'doe'}, + {username: 'Bob', password: 'doe'} + ], function(err, users) { + if (err) throw err; + + console.log('created users:', users); + cb(); + }); +}; diff --git a/examples/url-scheme/server/boot/root.js b/examples/credentials/server/boot/root.js similarity index 100% rename from examples/url-scheme/server/boot/root.js rename to examples/credentials/server/boot/root.js diff --git a/examples/credentials/server/component-config.json b/examples/credentials/server/component-config.json new file mode 100644 index 0000000..73da3c1 --- /dev/null +++ b/examples/credentials/server/component-config.json @@ -0,0 +1,19 @@ +{ + "loopback-component-explorer": { + "mountPath": "/explorer" + }, + "../node_modules/loopback-multitenancy": { + "scheme": "header", + "mountPath": "/:groupId/:resourceId", + "tenants": [ + { + "tenantId": "One", + "username": "Joe" + }, + { + "tenantId": "Two", + "username": "Bob" + } + ] + } +} diff --git a/examples/url-scheme/server/config.json b/examples/credentials/server/config.json similarity index 100% rename from examples/url-scheme/server/config.json rename to examples/credentials/server/config.json diff --git a/examples/credentials/server/datasources.json b/examples/credentials/server/datasources.json new file mode 100644 index 0000000..6d9dba1 --- /dev/null +++ b/examples/credentials/server/datasources.json @@ -0,0 +1,14 @@ +{ + "db1": { + "name": "db1", + "localStorage": "", + "file": "", + "connector": "memory" + }, + "db2": { + "name": "db2", + "localStorage": "", + "file": "", + "connector": "memory" + } +} diff --git a/examples/credentials/server/middleware.json b/examples/credentials/server/middleware.json new file mode 100644 index 0000000..365c633 --- /dev/null +++ b/examples/credentials/server/middleware.json @@ -0,0 +1,52 @@ +{ + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + }, + "helmet#xssFilter": {}, + "helmet#frameguard": { + "params": [ + "deny" + ] + }, + "helmet#hsts": { + "params": { + "maxAge": 0, + "includeSubdomains": true + } + }, + "helmet#hidePoweredBy": {}, + "helmet#ieNoOpen": {}, + "helmet#noSniff": {}, + "helmet#noCache": { + "enabled": false + } + }, + "session": {}, + "auth": { + "./middleware/basic-auth": {} + }, + "parse": {}, + "routes": { + "loopback#rest": { + "paths": [ + "${restApiRoot}" + ] + } + }, + "files": {}, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "loopback#errorHandler": {} + } +} diff --git a/examples/url-scheme/server/middleware.production.json b/examples/credentials/server/middleware.production.json similarity index 100% rename from examples/url-scheme/server/middleware.production.json rename to examples/credentials/server/middleware.production.json diff --git a/examples/credentials/server/middleware/basic-auth.js b/examples/credentials/server/middleware/basic-auth.js new file mode 100644 index 0000000..35a7338 --- /dev/null +++ b/examples/credentials/server/middleware/basic-auth.js @@ -0,0 +1,22 @@ +var auth = require('basic-auth'); + +module.exports = function() { + return function(req, res, next) { + var credentials = auth(req); + if (!credentials) + return next(new Error('Invalid credentials')); + + req.app.models.user.findOne({ + where: { + username: credentials.name + } + }, function(err, user) { + if (err) return next(err); + + if (!user) return next(new Error('Invalid credentials')); + + req.user = user; + next(); + }); + }; +}; diff --git a/examples/credentials/server/model-config.json b/examples/credentials/server/model-config.json new file mode 100644 index 0000000..ff5ec6d --- /dev/null +++ b/examples/credentials/server/model-config.json @@ -0,0 +1,28 @@ +{ + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ], + "mixins": [ + "loopback/common/mixins", + "loopback/server/mixins", + "../common/mixins", + "./mixins" + ] + }, + "user": { + "dataSource": "db1", + "public": true + }, + "One_Joe_Todo": { + "dataSource": "db1", + "public": true + }, + "Two_Bob_Todo": { + "dataSource": "db2", + "public": true + } +} diff --git a/examples/url-scheme/server/server.js b/examples/credentials/server/server.js similarity index 100% rename from examples/url-scheme/server/server.js rename to examples/credentials/server/server.js diff --git a/examples/credentials/server/tenants.json b/examples/credentials/server/tenants.json new file mode 100644 index 0000000..e69de29 diff --git a/examples/url/.editorconfig b/examples/url/.editorconfig new file mode 100644 index 0000000..3ee22e5 --- /dev/null +++ b/examples/url/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/examples/url/.gitignore b/examples/url/.gitignore new file mode 100644 index 0000000..aff1045 --- /dev/null +++ b/examples/url/.gitignore @@ -0,0 +1,19 @@ +*.csv +*.dat +*.iml +*.log +*.out +*.pid +*.seed +*.sublime-* +*.swo +*.swp +*.tgz +*.xml +.DS_Store +.idea +.project +.strong-pm +coverage +node_modules +npm-debug.log diff --git a/examples/url-scheme/.jshintignore b/examples/url/.jshintignore similarity index 100% rename from examples/url-scheme/.jshintignore rename to examples/url/.jshintignore diff --git a/examples/url-scheme/.jshintrc b/examples/url/.jshintrc similarity index 100% rename from examples/url-scheme/.jshintrc rename to examples/url/.jshintrc diff --git a/examples/url/.yo-rc.json b/examples/url/.yo-rc.json new file mode 100644 index 0000000..02f3fc1 --- /dev/null +++ b/examples/url/.yo-rc.json @@ -0,0 +1,3 @@ +{ + "generator-loopback": {} +} \ No newline at end of file diff --git a/examples/url-scheme/README.md b/examples/url/README.md similarity index 100% rename from examples/url-scheme/README.md rename to examples/url/README.md diff --git a/examples/url/client/README.md b/examples/url/client/README.md new file mode 100644 index 0000000..dd00c9e --- /dev/null +++ b/examples/url/client/README.md @@ -0,0 +1,3 @@ +## Client + +This is the place for your application front-end files. diff --git a/examples/url-scheme/common/models/one-joe-todo.js b/examples/url/common/models/one-joe-todo.js similarity index 100% rename from examples/url-scheme/common/models/one-joe-todo.js rename to examples/url/common/models/one-joe-todo.js diff --git a/examples/url/common/models/one-joe-todo.json b/examples/url/common/models/one-joe-todo.json new file mode 100644 index 0000000..835e6cf --- /dev/null +++ b/examples/url/common/models/one-joe-todo.json @@ -0,0 +1,17 @@ +{ + "name": "One_Joe_Todo", + "base": "PersistedModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": { + "content": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/examples/url-scheme/common/models/two-bob-todo.js b/examples/url/common/models/two-bob-todo.js similarity index 100% rename from examples/url-scheme/common/models/two-bob-todo.js rename to examples/url/common/models/two-bob-todo.js diff --git a/examples/url/common/models/two-bob-todo.json b/examples/url/common/models/two-bob-todo.json new file mode 100644 index 0000000..315077b --- /dev/null +++ b/examples/url/common/models/two-bob-todo.json @@ -0,0 +1,17 @@ +{ + "name": "Two_Bob_Todo", + "base": "PersistedModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": { + "content": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/examples/url-scheme/package.json b/examples/url/package.json similarity index 100% rename from examples/url-scheme/package.json rename to examples/url/package.json diff --git a/examples/url-scheme/server/boot/create-bob-sample-data.js b/examples/url/server/boot/create-bob-sample-data.js similarity index 100% rename from examples/url-scheme/server/boot/create-bob-sample-data.js rename to examples/url/server/boot/create-bob-sample-data.js diff --git a/examples/url-scheme/server/boot/create-joe-sample-data.js b/examples/url/server/boot/create-joe-sample-data.js similarity index 100% rename from examples/url-scheme/server/boot/create-joe-sample-data.js rename to examples/url/server/boot/create-joe-sample-data.js diff --git a/examples/url/server/boot/root.js b/examples/url/server/boot/root.js new file mode 100644 index 0000000..e106142 --- /dev/null +++ b/examples/url/server/boot/root.js @@ -0,0 +1,6 @@ +module.exports = function(server) { + // Install a `/` route that returns server status + var router = server.loopback.Router(); + router.get('/', server.loopback.status()); + server.use(router); +}; diff --git a/examples/url-scheme/server/component-config.json b/examples/url/server/component-config.json similarity index 53% rename from examples/url-scheme/server/component-config.json rename to examples/url/server/component-config.json index f8d63f8..7b40c1f 100644 --- a/examples/url-scheme/server/component-config.json +++ b/examples/url/server/component-config.json @@ -3,7 +3,7 @@ "mountPath": "/explorer" }, "../node_modules/loopback-multitenancy": { - "tenantResolverMountPath": "/api/:tenantId/:modelId/:modelName", - "modelResolverMountPath": "/api" + "scheme": "url", + "mountPath": "/:tenantId/:groupId/:resourceId" } } diff --git a/examples/url/server/config.json b/examples/url/server/config.json new file mode 100644 index 0000000..ac5e58c --- /dev/null +++ b/examples/url/server/config.json @@ -0,0 +1,27 @@ +{ + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3000, + "remoting": { + "context": { + "enableHttpContext": false + }, + "rest": { + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false, + "errorHandler": { + "disableStackTrace": false + } + }, + "legacyExplorer": false +} diff --git a/examples/url-scheme/server/datasources.json b/examples/url/server/datasources.json similarity index 100% rename from examples/url-scheme/server/datasources.json rename to examples/url/server/datasources.json diff --git a/examples/url-scheme/server/middleware.json b/examples/url/server/middleware.json similarity index 100% rename from examples/url-scheme/server/middleware.json rename to examples/url/server/middleware.json diff --git a/examples/url/server/middleware.production.json b/examples/url/server/middleware.production.json new file mode 100644 index 0000000..4572b0c --- /dev/null +++ b/examples/url/server/middleware.production.json @@ -0,0 +1,9 @@ +{ + "final:after": { + "loopback#errorHandler": { + "params": { + "includeStack": false + } + } + } +} diff --git a/examples/url-scheme/server/model-config.json b/examples/url/server/model-config.json similarity index 100% rename from examples/url-scheme/server/model-config.json rename to examples/url/server/model-config.json diff --git a/examples/url/server/server.js b/examples/url/server/server.js new file mode 100644 index 0000000..3be7b08 --- /dev/null +++ b/examples/url/server/server.js @@ -0,0 +1,27 @@ +var loopback = require('loopback'); +var boot = require('loopback-boot'); + +var app = module.exports = loopback(); + +app.start = function() { + // start the web server + return app.listen(function() { + app.emit('started'); + var baseUrl = app.get('url').replace(/\/$/, ''); + console.log('Web server listening at: %s', baseUrl); + if (app.get('loopback-component-explorer')) { + var explorerPath = app.get('loopback-component-explorer').mountPath; + console.log('Browse your REST API at %s%s', baseUrl, explorerPath); + } + }); +}; + +// Bootstrap the application, configure models, datasources and middleware. +// Sub-apps like REST API are mounted via boot scripts. +boot(app, __dirname, function(err) { + if (err) throw err; + + // start the server if `$ node server.js` + if (require.main === module) + app.start(); +}); diff --git a/index.js b/index.js index 625efea..b34a660 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,20 @@ var tenantResolver = require('./lib/tenant-resolver'); var modelResolver = require('./lib/model-resolver'); -module.exports = function(app, options) { - var tenantResolverMountPath = app.get('restApiRoot') + - '/:tenantId/:modelId/:modelName'; - app.middleware('parse', options.tenantResolverMountPath, tenantResolver); - - var modelResolverMountPath = app.get('restApiRoot'); - app.middleware('parse', options.modelResolverMountPath, modelResolver); +module.exports = function component(app, options) { + app.set('tenants', options.tenants); + registerTenantResolverMiddleware(app, options); + registerModelResolverMiddleware(app, options); }; + +function registerTenantResolverMiddleware(app, options) { + var mountPath = tenantResolver.getMountPath(app, options); + var middleware = tenantResolver.getResolver(options); + app.middleware('parse', mountPath, middleware); +} + +function registerModelResolverMiddleware(app, options) { + var mountPath = modelResolver.getMountPath(app, options); + var middleware = modelResolver.getResolver(options); + app.middleware('parse', mountPath, middleware); +} diff --git a/lib/model-resolver.js b/lib/model-resolver.js deleted file mode 100644 index ea7fdba..0000000 --- a/lib/model-resolver.js +++ /dev/null @@ -1,13 +0,0 @@ -var f = require('util').format; - -module.exports = function modelResolver(req, res, next) { - if (!req.tenant) - return next(); - - var namespacedUrl = f('/%s_%s_%s', - req.tenant.id, - req.tenant.modelId, - req.tenant.modelName); - req.url = namespacedUrl; - next(); -}; diff --git a/lib/model-resolver/index.js b/lib/model-resolver/index.js new file mode 100644 index 0000000..fe5c9b7 --- /dev/null +++ b/lib/model-resolver/index.js @@ -0,0 +1,15 @@ +var modelResolverMiddleware = require('./resolver'); +var validate = require('../validation'); + +exports.getMountPath = getMountPath; +exports.getResolver = getResolver; + +function getMountPath(app, options) { + var restApiRoot = app.get('restApiRoot') || 'url'; + validate.restApiRoot(restApiRoot); + return restApiRoot; +} + +function getResolver() { + return modelResolverMiddleware; +} diff --git a/lib/model-resolver/resolver.js b/lib/model-resolver/resolver.js new file mode 100644 index 0000000..5565545 --- /dev/null +++ b/lib/model-resolver/resolver.js @@ -0,0 +1,11 @@ +var format = require('util').format; + +module.exports = function modelResolverMiddleware(req, res, next) { + if (!req.tenant) + return next(); + + var namespacedUrl = format('/%s_%s_%s', + req.tenant.id, req.tenant.groupId, req.tenant.resourceId); + req.url = namespacedUrl; + next(); +}; diff --git a/lib/tenant-resolver.js b/lib/tenant-resolver.js deleted file mode 100644 index 2f7657f..0000000 --- a/lib/tenant-resolver.js +++ /dev/null @@ -1,32 +0,0 @@ -var f = require('util').format; - -module.exports = function tenantResolver(req, res, next) { - // validation - var tenantParams = [ - {name: 'tenantId', type: 'string'}, - {name: 'modelId', type: 'string'}, - {name: 'modelName', type: 'string'}, - ]; - var err; - for (var i = 0, n = tenantParams.length; i < n; i++) { - var tenantName = tenantParams[i].name; - var param = req.params[tenantName]; - var tenantType = tenantParams[i].type; - if (!param || typeof param !== tenantType) { - var errMsg = f('Invalid %s. Expected %s, got %s.', - tenantName, tenantType, typeof param); - err = new Error(errMsg); - break; - } - } - - if (err) - return next(err); - - req.tenant = { - id: req.params.tenantId, - modelId: req.params.modelId, - modelName: req.params.modelName, - }; - next(); -}; diff --git a/lib/tenant-resolver/index.js b/lib/tenant-resolver/index.js new file mode 100644 index 0000000..abb303e --- /dev/null +++ b/lib/tenant-resolver/index.js @@ -0,0 +1,21 @@ +var validate = require('../validation'); + +exports.getMountPath = getMountPath; +exports.getResolver = getResolver; + +function getMountPath(app, options) { + var restApiRoot = app.get('restApiRoot') || 'url'; + validate.restApiRoot(restApiRoot); + var mountPath = options.mountPath || ''; + validate.mountPath(mountPath); + // normalize mountPath for windows + return restApiRoot + mountPath; +} + +function getResolver(options) { + var scheme = options.scheme || 'url'; + validate.scheme(scheme); + // use path to normalize the /'s in the require + var schemeBasedTenantResolverMiddleware = require('./resolvers/' + scheme); + return schemeBasedTenantResolverMiddleware; +} diff --git a/lib/tenant-resolver/resolvers/header.js b/lib/tenant-resolver/resolvers/header.js new file mode 100644 index 0000000..e23a7d4 --- /dev/null +++ b/lib/tenant-resolver/resolvers/header.js @@ -0,0 +1,18 @@ +module.exports = function headerResolverMiddleware(req, res, next) { + var tenants = req.app.get('tenants'); + var tenant; + for (var i = 0; i < tenants.length; i++) { + if (tenants[i].username === req.user.username) { + tenant = tenants[i]; + } + } + if (!tenant) + return next(new Error('Invalid tenant')); + + req.tenant = { + id: tenant.tenantId, + groupId: req.params.groupId, + resourceId: req.params.resourceId, + }; + next(); +}; diff --git a/lib/tenant-resolver/resolvers/url.js b/lib/tenant-resolver/resolvers/url.js new file mode 100644 index 0000000..2c84f01 --- /dev/null +++ b/lib/tenant-resolver/resolvers/url.js @@ -0,0 +1,14 @@ +var f = require('util').format; +var validate = require('../../validation'); + +module.exports = function urlResolverMiddleware(req, res, next) { + // validate tenantId in req.params + // validate groupId in req.params + // validate resourceId in req.params + req.tenant = { + id: req.params.tenantId, + groupId: req.params.groupId, + resourceId: req.params.resourceId, + }; + next(); +}; diff --git a/lib/validation.js b/lib/validation.js new file mode 100644 index 0000000..58ffcdb --- /dev/null +++ b/lib/validation.js @@ -0,0 +1,38 @@ +exports.mountPath = mountPath; +exports.params = validateParams; +exports.restApiRoot = restApiRoot; +exports.scheme = validateScheme; + +function mountPath(mountPath) { + //should be a valid url or default to '' (because this gets appended to + //restApiRoot -- ie. /api + '') +} + +function restApiRoot(restApiRoot) { + // should be a valid url or default to /api +} + +function validateParams(validParams, givenParams) { + for (var i = 0; i < validParams.length; i++) { + var validParamName = validParams[i].name; + var param = given[validParamName]; + var validParamType = validParams[i].type; + if (typeof param !== validParamType) + return new Error(f('Invalid %s. Expected %s, got %s.', + validParamName, validParamType, typeof param)); + } +} + +function validateScheme(scheme) { + // should loop the resolvers dir to get valid schemes array based on filenames + // without .js extension (filenames === scheme names) + // this will allow us to copy paste new schemes without modifying anything else in the module + var validSchemes = ['header', 'url', 'virtual-host']; + // use array.includes instead once node6 is our recommended version + var isValidScheme = validSchemes.indexOf(scheme) !== -1; + if (!isValidScheme) + // loop resolvers dir to form error message listing valid schemes + // the name of the scheme should be the same as the filename without .js + throw new Error('Invalid scheme `' + scheme + '`. Must be `header`, ' + + '`url` or `virtual-host`'); +}