Skip to content

Commit

Permalink
feat: initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
gr2m committed Dec 15, 2015
1 parent fcefc99 commit ad09a62
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 0 deletions.
32 changes: 32 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module.exports = {
installUsersBehavior: installUsersBehavior
}

var createBulkDocsWrapper = require('pouchdb-bulkdocs-wrapper')
var Promise = require('lie')
var wrappers = require('pouchdb-wrappers')

var modifyDoc = require('./lib/modify-doc')

function installUsersBehavior () {
var db = this

return new Promise(function (resolve) {
var writeWrappers = {}

writeWrappers.put = function (original, args) {
return modifyDoc(args.doc).then(original)
}
writeWrappers.post = writeWrappers.put
writeWrappers.bulkDocs = createBulkDocsWrapper(modifyDoc)

wrappers.installWrapperMethods(db, writeWrappers)

resolve()
})
}

/* istanbul ignore next */
if (typeof window !== 'undefined' && window.PouchDB) {
window.PouchDB.plugin(module.exports)
}
17 changes: 17 additions & 0 deletions lib/generate-salt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = generateSalt

var secureRandom = require('secure-random')

function generateSalt () {
var array = secureRandom(16)
var result = arrayToString(array)
return Promise.resolve(result)
}

function arrayToString (array) {
var result = ''
for (var i = 0; i < array.length; i += 1) {
result += ((array[i] & 0xFF) + 0x100).toString(16)
}
return result
}
15 changes: 15 additions & 0 deletions lib/hash-password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = hashPassword

var crypto = require('crypto')

function hashPassword (password, salt, iterations) {
return new Promise(function (resolve, reject) {
crypto.pbkdf2(password, salt, iterations, 20, function (err, derivedKey) {
if (err) {
reject(err)
} else {
resolve(derivedKey.toString('hex'))
}
})
})
}
29 changes: 29 additions & 0 deletions lib/modify-doc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module.exports = modifyDoc

var generateSalt = require('./generate-salt')
var hashPassword = require('./hash-password')
var validateDocUpdate = require('./validate-doc-update')

function modifyDoc (doc) {
try {
validateDocUpdate(doc)
} catch (error) {
return Promise.reject(error)
}

if (typeof doc.password === 'undefined' || doc.password === null) {
return Promise.resolve()
}

doc.iterations = 10
doc.password_scheme = 'pbkdf2'

return generateSalt().then(function (salt) {
doc.salt = salt

return hashPassword(doc.password, doc.salt, doc.iterations)
}).then(function (hash) {
delete doc.password
doc.derived_key = hash
})
}
65 changes: 65 additions & 0 deletions lib/validate-doc-update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module.exports = function (newDoc, oldDoc, userCtx, secObj) {
var i

function throwError (message) {
var error = new Error(message)
error.name = 'forbidden'
throw error
}

if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') {
throwError('doc.type must be user')
} // we only allow user docs for now

if (!newDoc.name) {
throwError('doc.name is required')
}

if (!newDoc.roles) {
throwError('doc.roles must exist')
}

if (!Array.isArray(newDoc.roles)) {
throwError('doc.roles must be an array')
}

for (var idx = 0; idx < newDoc.roles.length; idx++) {
if (typeof newDoc.roles[idx] !== 'string') {
throwError('doc.roles can only contain strings')
}
}

if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) {
throwError('Doc ID must be of the form org.couchdb.user:name')
}

if (oldDoc) { // validate all updates
if (oldDoc.name !== newDoc.name) {
throwError('Usernames can not be changed.')
}
}

if (newDoc.password_sha && !newDoc.salt) {
throwError('Users with password_sha must have a salt. See /_utils/script/couch.js for example code.')
}

// no system roles in users db
for (i = 0; i < newDoc.roles.length; i++) {
if (newDoc.roles[i][0] === '_') {
throwError('No system roles (starting with underscore) in users db.')
}
}

// no system names as names
if (newDoc.name[0] === '_') {
throwError('Username may not start with underscore.')
}

var badUserNameChars = [':']

for (i = 0; i < badUserNameChars.length; i++) {
if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) {
throwError('Character `' + badUserNameChars[i] + '` is not allowed in usernames.')
}
}
}

0 comments on commit ad09a62

Please sign in to comment.