Skip to content

Commit

Permalink
Merge pull request #48 from domapic/release-1.0.0-alpha.14
Browse files Browse the repository at this point in the history
Release 1.0.0 alpha.14
  • Loading branch information
javierbrea committed Jan 6, 2019
2 parents 6933464 + b5ee85b commit bbfe726
Show file tree
Hide file tree
Showing 29 changed files with 571 additions and 84 deletions.
29 changes: 29 additions & 0 deletions .narval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,35 @@ suites:
- test/functional/specs/db-config.specs.js
- test/functional/specs/service-registerer-api-key.specs.js
coverage: *base-coverage
- name: authentication-disabled
describe: Api should work as expected when authentication is disabled
before: *base-before
services:
- name: mongodb
docker:
container: mongodb-container
command: test/functional/commands/mongodb-docker.sh
- name: controller
docker:
container: controller-container
command: test/functional/commands/cli-start-auth-disabled.sh
wait-on: tcp:mongodb-container:27017
env:
controller_host_name: controller-container
domapic_path: .shared
db_uri: mongodb://mongodb-container:27017/domapic
local:
command: test/functional/commands/cli-start-auth-disabled.sh
wait-on: tcp:localhost:27017
env:
controller_host_name: localhost
domapic_path: .test
db_uri: mongodb://localhost:27017/domapic
test:
<<: *base-test
specs:
- test/functional/specs/authentication-disabled-api.specs.js
coverage: *base-coverage
- name: users
describe: Users api should work as expected
before: *base-before
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
### Removed

## [1.0.0-alpha.14] - 2019-01-06
### Added
- Add anonymous default user, which will be used as logged user for requests with authentication disabled. When this user is logged in, services and abilities will be added to user with same name as service, not to logged user. In this way, the services connection process will work when authentication is disabled, and services registered will still be connected if authentication is enabled again.

### Fixed
- Fix enum for abilities with numeric data type.

## [1.0.0-alpha.13] - 2018-12-17
### Fixed
- Return new documents in update commands.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Controller server for Domapic domotic systems.
Connect all your Domapic Modules and control them with a single application. <!-- and program them to interact automatically using the provided web interface
Install Domapic plugins to connect Domapic with other domotic systems or online services. -->

__NOTE: The next schema includes some Domapic pieces that are still not released. The web ui for the Controller, Domapic Cloud, mobile apps, as well as Homekit and Alexa plugins will be available soon.__
__NOTE: The next schema includes some Domapic pieces that are still not released. The web ui for the Controller, Domapic Cloud, mobile apps and Alexa plugins will be available soon.__

![Domapic system example][domapic-example-image]

Expand Down Expand Up @@ -140,6 +140,7 @@ option | description | default
`--sslCert` | Path to an ssl certificate | -
`--sslKey` | Path to an ssl key | -
`--authDisabled` | Array of IPs or CIDR IP ranges with authentication disabled | ['127.0.0.1', '::1/128']
`--auth` | If false, authentication will be disabled for all origins | true
`--secret` | Secret to be used in authentication encoding | -
`--color` | Use ANSI colors in traces | true
`--logLevel` | Tracing level. Choices are 'log', 'trace', 'debug', 'info', 'warn' and 'error' | info
Expand Down Expand Up @@ -182,7 +183,7 @@ Follow the next steps to securize your Controller before exposing it to the inte

* __Disable the authentication whitelist:__

Authentication can be disabled for desired IPs or IP ranges using the `--authDisabled` option. By default, authentication is disabled only for the 172.0.0.1 IP, in order to make easier the first configuration, but you can disable it for all your local network, etc. Because of security reasons, this is not recommended. Use always the built-in api keys method to identify your Domapic Services.
Authentication can be disabled for desired IPs or IP ranges using the `--authDisabled` option, or for all origins using the `--auth=false` option. By default, authentication is disabled only for the 172.0.0.1 IP in order to make easier the first configuration, but you can disable it for your whole your local network, etc. *Because of security reasons, this is not recommended*, take into account that users accessing to the controller with authentication disabled will have equivalent permissions to an user with "admin" role. Use always the built-in api keys method to identify your Domapic Services.
If you want to force the authentication requirement even for localhost, use the `--authDisabled` as a flag, without specifying any IP.

[domapic-logo-image]: http://domapic.com/assets/domapic-logo.png
Expand Down
5 changes: 5 additions & 0 deletions lib/Security.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

const jwt = require('./security/jwt')
const apiKey = require('./security/apiKey')
const disabled = require('./security/disabled')

const Security = (service, commands) => {
const jwtMethods = jwt.Methods(service, commands)
const apiKeyMethods = apiKey.Methods(service, commands)
const disabledMethods = disabled.Methods(service, commands)

const methods = () => service.config.get('secret').then(secret => ({
jwt: {
Expand All @@ -29,6 +31,9 @@ const Security = (service, commands) => {
auth: apiKeyMethods.revokeAuth,
handler: apiKeyMethods.revokeHandler
}
},
disabled: {
verify: disabledMethods.verify
}
}))

Expand Down
13 changes: 7 additions & 6 deletions lib/api/abilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ const Operations = (service, commands) => {
},
addAbility: {
auth: (userData) => userData.role === roles.MODULE,
handler: (params, body, res, userData) => commands.ability.add(userData, body).then(abilityData => {
res.status(201)
res.header(LOCATION, `${LOCATION_ROOT}${abilityData._id}`)
events.plugin(EVENT_ENTITY, events.OPERATIONS.CREATE, abilityData)
return Promise.resolve()
})
handler: (params, body, res, userData) => commands.composed.getAbilityOwner(userData, body)
.then(abilityOwner => commands.ability.add(abilityOwner, body).then(abilityData => {
res.status(201)
res.header(LOCATION, `${LOCATION_ROOT}${abilityData._id}`)
events.plugin(EVENT_ENTITY, events.OPERATIONS.CREATE, abilityData)
return Promise.resolve()
}))
},
deleteAbility: {
auth: onlyOwner,
Expand Down
5 changes: 5 additions & 0 deletions lib/api/abilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@
"type": "string",
"pattern": "^[a-zA-Z0-9_-]*$"
},
"_service": {
"description": "Unique id of the service owner of the ability",
"type": "string"
},
"description": {
"description": "Description of the ability",
"type": "string"
Expand Down Expand Up @@ -245,6 +249,7 @@
"additionalProperties": false,
"example": {
"name": "foo-ability-name",
"_service": "foo-service-id",
"description": "Rele control",
"event": true,
"eventDescription": "Rele has changed state",
Expand Down
15 changes: 8 additions & 7 deletions lib/api/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ const Operations = (service, commands) => ({
},
addService: {
auth: (userData, params, body) => (userData.role === roles.MODULE || userData.role === roles.PLUGIN) && body.type === userData.role,
handler: (params, body, res, userData) => commands.service.add(userData, body)
.then(serviceData => {
res.status(201)
res.header('location', `/api/services/${serviceData._id}`)
events.plugin(EVENT_ENTITY, events.OPERATIONS.CREATE, serviceData)
return Promise.resolve()
})
handler: (params, body, res, userData) => commands.composed.getServiceOwner(userData, body)
.then(serviceOwner => commands.service.add(serviceOwner, body)
.then(serviceData => {
res.status(201)
res.header('location', `/api/services/${serviceData._id}`)
events.plugin(EVENT_ENTITY, events.OPERATIONS.CREATE, serviceData)
return Promise.resolve()
}))
}
})

Expand Down
5 changes: 5 additions & 0 deletions lib/api/services.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
"description": "New service data",
"type": "object",
"properties": {
"name": {
"description": "Name of the service",
"type": "string",
"pattern": "^[a-z0-9_.-]*$"
},
"processId": {
"description": "Unique service process identifier. Auto-generated by service instance when started up",
"type": "string"
Expand Down
25 changes: 23 additions & 2 deletions lib/commands/composed.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const templates = require('../templates')
const { SERVICE_REGISTERER_USER, API_KEY } = require('../security/utils')
const { SERVICE_REGISTERER_USER, API_KEY, ANONYMOUS_USER } = require('../security/utils')

const Commands = (service, models, client, commands) => {
const initUsers = () => commands.user.init()
Expand Down Expand Up @@ -46,11 +46,32 @@ const Commands = (service, models, client, commands) => {
])
}))

const getServiceOwner = (userData, serviceData) => {
if (userData.name === ANONYMOUS_USER.name) {
return commands.user.get({
name: serviceData.name
})
}
return Promise.resolve(userData)
}

const getAbilityOwner = (userData, ability) => {
if (userData.name === ANONYMOUS_USER.name) {
return commands.service.getById(ability._service)
.then(serviceData => Promise.resolve({
_id: serviceData._user
}))
}
return Promise.resolve(userData)
}

return {
initUsers,
dispatchAbilityAction,
triggerAbilityEvent,
getAbilityState
getAbilityState,
getServiceOwner,
getAbilityOwner
}
}

Expand Down
5 changes: 3 additions & 2 deletions lib/commands/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const templates = require('../templates')
const utils = require('../utils')
const { INITIAL_ADMIN_USER, SERVICE_REGISTERER_USER } = require('../security/utils')
const { INITIAL_ADMIN_USER, SERVICE_REGISTERER_USER, ANONYMOUS_USER } = require('../security/utils')

const PUBLIC_FIELDS = 'name email role updatedAt createdAt'

Expand Down Expand Up @@ -37,7 +37,8 @@ const Commands = (service, models, client) => {

const init = () => getAll().then(users => users.length ? Promise.resolve() : Promise.all([
add(INITIAL_ADMIN_USER),
add(SERVICE_REGISTERER_USER)
add(SERVICE_REGISTERER_USER),
add(ANONYMOUS_USER)
]))

return {
Expand Down
2 changes: 1 addition & 1 deletion lib/models/ability.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const Model = service => {
enum: ['date-time', 'email', 'hostname', 'ipv4', 'ipv6', 'uri']
},
enum: {
type: [String],
type: Array,
default: undefined
},
maxLength: {
Expand Down
4 changes: 2 additions & 2 deletions lib/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const mongoose = require('mongoose')

const templates = require('../templates')
const utils = require('../utils')
const { roles } = require('../security/utils')
const { roles, ANONYMOUS_USER } = require('../security/utils')

const MODEL_NAME = 'User'

Expand All @@ -18,7 +18,7 @@ const Model = service => {
const models = {}

const requireEmailAndPassword = function () {
return ![roles.MODULE, roles.SERVICE_REGISTERER, roles.PLUGIN].includes(this.role)
return this.name !== ANONYMOUS_USER.name && ![roles.MODULE, roles.SERVICE_REGISTERER, roles.PLUGIN].includes(this.role)
}

const uniqueEmailValidator = utils.ValidateUniqueModel(models, MODEL_NAME, 'email', templates.emailAlreadyExists())
Expand Down
25 changes: 25 additions & 0 deletions lib/security/disabled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

const utils = require('./utils')

const Methods = (service, commands) => {
let anonymousUserPromise = null

const getAnonymousUser = () => {
if (anonymousUserPromise) {
return anonymousUserPromise
}
anonymousUserPromise = commands.user.get({
name: utils.ANONYMOUS_USER.name
}).then(userData => Promise.resolve(utils.cleanUserData(userData)))
return anonymousUserPromise
}

return {
verify: getAnonymousUser
}
}

module.exports = {
Methods
}
6 changes: 6 additions & 0 deletions lib/security/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const SERVICE_REGISTERER_USER = {
role: roles.SERVICE_REGISTERER
}

const ANONYMOUS_USER = {
name: 'anonymous',
role: roles.ADMIN
}

const API_KEY = 'apiKey'
const JWT = 'jwt'

Expand Down Expand Up @@ -49,6 +54,7 @@ module.exports = {
roles,
INITIAL_ADMIN_USER,
SERVICE_REGISTERER_USER,
ANONYMOUS_USER,
API_KEY,
JWT,
cleanUserData,
Expand Down
Loading

0 comments on commit bbfe726

Please sign in to comment.