Skip to content

Commit

Permalink
Merge pull request #344 from dadi/feature/connection-recover
Browse files Browse the repository at this point in the history
DB connection recovery (and more!)
  • Loading branch information
eduardoboucas authored Oct 30, 2017
2 parents 7a98b86 + e85016f commit 6da1898
Show file tree
Hide file tree
Showing 23 changed files with 4,670 additions and 3,172 deletions.
7 changes: 7 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,13 @@ var conf = convict({
doc: 'The character to be used for prefixing internal fields',
format: 'String',
default: '_'
},
databaseConnection: {
maxRetries: {
doc: "The maximum number of times to reconnection attempts after a database fails",
format: Number,
default: 10
}
}
})

Expand Down
10 changes: 9 additions & 1 deletion config/mongodb.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
"ssl": false,
"replicaSet": "",
"enableCollectionDatabases": false,
"testdb": {
"hosts": [
{
"host": "127.0.0.1",
"port": 27017
}
]
},
"test_auth_db": {
"hosts": [
{
Expand All @@ -36,4 +44,4 @@
"replicaSet": "",
"ssl": false
}
}
}
71 changes: 39 additions & 32 deletions dadi/lib/auth/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
var _ = require('underscore')
var path = require('path')
var url = require('url')
'use strict'

var config = require(path.join(__dirname, '/../../../config.js'))
var tokens = require(path.join(__dirname, '/tokens'))
var pathToRegexp = require('path-to-regexp')
const _ = require('underscore')
const path = require('path')
const url = require('url')

const config = require(path.join(__dirname, '/../../../config.js'))
const formatError = require('@dadi/format-error')
const tokens = require(path.join(__dirname, '/tokens'))
const pathToRegexp = require('path-to-regexp')

function mustAuthenticate (endpoints, req) {
var parsedUrl = url.parse(req.url, true)
const parsedUrl = url.parse(req.url, true)

// all /config requests must be authenticated
if (parsedUrl.pathname.indexOf('config') > -1) return true
Expand All @@ -18,13 +21,13 @@ function mustAuthenticate (endpoints, req) {
// docs requests don't need to be authenticated
if (parsedUrl.pathname.indexOf('docs') > 0) return false

var endpointKey = _.find(_.keys(endpoints), function (k) {
const endpointKey = _.find(_.keys(endpoints), function (k) {
return parsedUrl.pathname.match(pathToRegexp(k))
})

if (!endpointKey) return true

var endpoint = endpoints[endpointKey]
const endpoint = endpoints[endpointKey]

if (isMediaEndpoint(endpoint, req)) {
if (req.params.token && req.params.token === 'sign') {
Expand Down Expand Up @@ -60,23 +63,12 @@ function isMediaEndpoint (endpoint, req) {
}

function isAuthorized (endpoints, req, client) {
var path = url.parse(req.url, true)

var urlParts = _.compact(path.pathname.split('/'))
var version = urlParts.shift()
const path = url.parse(req.url, true)

var endpointKey = _.find(_.keys(endpoints), function (k) { return k.indexOf(path.pathname) > -1 })
const urlParts = _.compact(path.pathname.split('/'))
const version = urlParts.shift()

// check if this is a master config request first
// if (path.pathname.indexOf('api/config') > -1 && client.permissions) {
// if (client.permissions.collections && client.permissions.collections.indexOf(path.pathname) < 0) {
// return false
// }

// if (client.permissions.endpoints && client.permissions.endpoints.indexOf(path.pathname) < 0) {
// return false
// }
// }
const endpointKey = _.find(_.keys(endpoints), function (k) { return k.indexOf(path.pathname) > -1 })

// check if user accessType allows access to collection config
if (path.pathname.indexOf('config') > -1 && req.method === 'POST') {
Expand All @@ -89,7 +81,7 @@ function isAuthorized (endpoints, req, client) {

if (!endpointKey || !client.permissions) return true

var authorized = true
let authorized = true

if (endpoints[endpointKey].model && client.permissions.collections) {
authorized = _.findWhere(client.permissions.collections, { path: endpoints[endpointKey].model.name })
Expand All @@ -108,7 +100,7 @@ function isAuthorized (endpoints, req, client) {

// This attaches middleware to the passed in app instance
module.exports = function (server) {
var tokenRoute = config.get('auth.tokenUrl') || '/token'
const tokenRoute = config.get('auth.tokenUrl') || '/token'

// Authorize
server.app.use(function (req, res, next) {
Expand All @@ -119,8 +111,9 @@ module.exports = function (server) {
if (!(req.headers && req.headers.authorization)) return fail()

// Strip token value out of request headers
var parts = req.headers.authorization.split(' ')
var token
const parts = req.headers.authorization.split(' ')

let token

// Headers should be `Authorization: Bearer <%=tokenvalue%>`
if (parts.length === 2 && /^Bearer$/i.test(parts[0])) {
Expand All @@ -130,18 +123,31 @@ module.exports = function (server) {
if (!token) return fail('NoToken')

tokens.validate(token, function (err, client) {
if (err) return next(err)
if (err) {
if (err.message === 'DB_DISCONNECTED') {
const error = formatError.createApiError('0004')

error.statusCode = 503

return next(error)
}

return next(err)
}

// If token is good continue, else `fail()`
if (client) {
if (!isAuthorized(server.components, req, client)) {
var error = new Error('ClientId not authorized to access requested collection.')
const error = new Error('ClientId not authorized to access requested collection.')
error.statusCode = 401

res.setHeader('WWW-Authenticate', 'Bearer realm="' + url.parse(req.url, true).pathname + '"')

return next(error)
} else {
// Token is valid attach client to request
// Token is valid. Attach client to request.
req.client = client

return next()
}
}
Expand All @@ -150,7 +156,8 @@ module.exports = function (server) {
})

function fail (type) {
var err = new Error('Unauthorized')
const err = new Error('Unauthorized')

err.statusCode = 401

if (type === 'InvalidToken') {
Expand Down
24 changes: 21 additions & 3 deletions dadi/lib/auth/tokenStore.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
'use strict'

const debug = require('debug')('api:tokenStore')
const log = require('@dadi/logger')
const path = require('path')
const Connection = require(path.join(__dirname, '/../model/connection'))
const config = require(path.join(__dirname, '/../../../config.js'))
const uuid = require('uuid')

const TokenStore = function () {
this.recoveringFromDBDisconnect = false
this.collection = config.get('auth.tokenCollection')
this.database = config.get('auth.database')
this.databaseName = config.get('auth.database')
this.schema = {
fields: {
token: {
Expand Down Expand Up @@ -49,11 +51,17 @@ TokenStore.prototype.connect = function () {
const dbOptions = {
override: true,
collection: this.collection,
database: this.database
database: this.databaseName
}

this.connection = Connection(dbOptions, null, config.get('auth.datastore'))
this.connection.once('connect', database => {
if (this.recoveringFromDBDisconnect) {
this.recoveringFromDBDisconnect = false

return resolve(this.connect())
}

// Initialise cleanup agent
this.startCleanupAgent()

Expand All @@ -67,6 +75,14 @@ TokenStore.prototype.connect = function () {
}
]).then(result => resolve(database))
})

this.connection.once('disconnect', err => {
log.error({module: 'tokenStore'}, err)

this.recoveringFromDBDisconnect = true

return reject(new Error('DB_DISCONNECTED'))
})
})

return this.database
Expand Down Expand Up @@ -181,7 +197,9 @@ let tokenStore
module.exports = () => {
if (!tokenStore) {
tokenStore = new TokenStore()
tokenStore.connect()
tokenStore.connect().catch(err => {
log.error({module: 'tokenStore'}, err)
})
}

return tokenStore
Expand Down
4 changes: 4 additions & 0 deletions dadi/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var _ = require('underscore')
var api = require(path.join(__dirname, '/api'))
var auth = require(path.join(__dirname, '/auth'))
var cache = require(path.join(__dirname, '/cache'))
var Connection = require(path.join(__dirname, '/model/connection'))
var Controller = require(path.join(__dirname, '/controller'))
var HooksController = require(path.join(__dirname, '/controller/hooks'))
var MediaController = require(path.join(__dirname, '/controller/media'))
Expand Down Expand Up @@ -255,6 +256,9 @@ Server.prototype.stop = function (done) {

this.server.close(function (err) {
self.readyState = 0

Connection.resetConnections()

done && done(err)
})
}
Expand Down
Loading

0 comments on commit 6da1898

Please sign in to comment.