Skip to content

Commit

Permalink
fixup
Browse files Browse the repository at this point in the history
  • Loading branch information
nknapp committed Nov 20, 2017
1 parent c1a2b0f commit 5f5b52f
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 46 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,4 +1,5 @@
node_modules
*.iml
/tmp
/coverage
/coverage
/test-tmp
14 changes: 10 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -54,6 +54,8 @@
"istanbul": "^1.1.0-alpha.1",
"mocha": "^4.0.1",
"mock-fs": "^4.4.2",
"popsicle": "^9.2.0",
"spy": "^1.0.0",
"thoughtful-release": "^0.3.1",
"trace-and-clarify-if-possible": "^1.0.1"
},
Expand Down
25 changes: 19 additions & 6 deletions src/router.js
Expand Up @@ -16,13 +16,13 @@ function createRouter (postfixAccounts) {
*/
router.post('/user/:username', async function (req, res, next) {
try {

const username = req.params.username
const {oldPassword, newPassword} = req.body
if (oldPassword == null || newPassword == null) {
throw new BadRequestError('Request must contain username, oldPassword and newPassword')
}
await postfixAccounts.verifyAndUpdateUserPassword(username, oldPassword, newPassword)
res.contentType('application/json')
res.send(JSON.stringify({
success: true,
username
Expand All @@ -39,12 +39,14 @@ function createRouter (postfixAccounts) {
// eslint-disable-next-line no-console
console.error('Error', err.message, err.stack)
if (err instanceof AuthenticationError) {
res.status(403).send('Bad username or password')
} else if (err instanceof BadRequestError) {
res.status(400).send(err.message)
} else {
res.status(500).send('Internal server error')
return sendError(res, 403, 'Bad username or password')
}
/* istanbul ignore else */
if (err instanceof BadRequestError) {
return sendError(res, 400, err.message)
}
/* istanbul ignore next */
return sendError(res, 500, 'Internal server error')
})

return router
Expand All @@ -53,4 +55,15 @@ function createRouter (postfixAccounts) {
class BadRequestError extends Error {
}

function sendError (response, responseCode, message) {
response
.status(responseCode)
.contentType('application/json')
.send(JSON.stringify({
success: false,
code: responseCode,
message: message
}))
}

module.exports = {createRouter}
53 changes: 53 additions & 0 deletions test/_utils.js
@@ -0,0 +1,53 @@
const pify = require('pify')
const crypt = require('crypt3')
const cpr = pify(require('cpr'))

// Constant salt used for hashes in these test cases
const SALT = '$6$V5oMyA+u8Q2U/g=='

// Original contents of the fixture "postfix-accounts.cf"
const fixture = {
'mailtest@test.knappi.org': hashFor('abc', '$6$UeXF8rxTS/a7bHrp'),
'railtest@test.knappi.org': hashFor('abcd', '$6$y628bqC.aK2m.ncq')
}

/**
* Compute expected hash for a password
* @param {string} password the password
* @param {string=} salt the salt. Default is the mock SALT used in most tests
*/
function hashFor (password, salt) {
return `{SHA512-CRYPT}${crypt(password, salt || SALT)}`
}

/**
* Setup beforeEach and afterEach hooks that are generally needed.
*
* These do the following things:
*
* * Setup the mock salt to ensure deterministic hashes
* * Restore the original salt function after each test
* * Setup the tmp-directory before each test
*/
function mochaSetup () {
/* eslint-env mocha */
let originalSHA512Salter

beforeEach(async function () {
await cpr('test/fixtures', 'test-tmp/fixtures', {deleteFirst: true})

// mock "createSalt" to get deterministic hashes
originalSHA512Salter = crypt.createSalt.salters['sha512']
crypt.createSalt.salters['sha512'] = () => SALT
})

afterEach(function () {
crypt.createSalt.salters['sha512'] = originalSHA512Salter
})
}

module.exports = {
fixture,
hashFor,
mochaSetup
}
2 changes: 2 additions & 0 deletions test/fixtures/postfix-accounts-mailtest-ab.cf
@@ -0,0 +1,2 @@
railtest@test.knappi.org|{SHA512-CRYPT}$6$y628bqC.aK2m.ncq$/f9ARypMSviNXMD1ZqdFO6B9Vl8O6X.7ZIauNm34bpUCWnDg91C9OgcnQ/7XZh7rCt1JPQfc/g/vpRdWTqbp0/
mailtest@test.knappi.org|{SHA512-CRYPT}$6$UeXF8rxTS/a7bHrp$yQaj.9fgyDckIP3pgspd6YKUsyN8K54Am3n5kSpYwFG3C1gHKAM4MlfCcBkJsd5vB/UNAPfUlA6ShOIQa4Vmr/
43 changes: 8 additions & 35 deletions test/postfix-accounts-spec.js
Expand Up @@ -8,34 +8,14 @@
/* eslint-env mocha */

const {PostfixAccounts, UserExistsError, NoUserError, AuthenticationError} = require('../src/postfix-accounts')
const crypt = require('crypt3')
const fs = require('fs')
const pify = require('pify')
const cpr = pify(require('cpr'))
const {mochaSetup, fixture, hashFor} = require('./_utils')

const chai = require('chai')
chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised'))
const expect = chai.expect

// Constant salt used for hashes in these test cases
const SALT = '$6$V5oMyA+u8Q2U/g=='

// Original contents of the fixture "postfix-accounts.cf"
const fixture = {
'mailtest@test.knappi.org': '{SHA512-CRYPT}$6$UeXF8rxTS/a7bHrp$yQaj.9fgyDckIP3pgspd6YKUsyN8K54Am3n5kSpYwFG3C1gHKAM4MlfCcBkJsd5vB/UNAPfUlA6ShOIQa4Vmr/',
'railtest@test.knappi.org': '{SHA512-CRYPT}$6$y628bqC.aK2m.ncq$/f9ARypMSviNXMD1ZqdFO6B9Vl8O6X.7ZIauNm34bpUCWnDg91C9OgcnQ/7XZh7rCt1JPQfc/g/vpRdWTqbp0/'
}

const fixtureOnlyRailtest = {
'railtest@test.knappi.org': '{SHA512-CRYPT}$6$y628bqC.aK2m.ncq$/f9ARypMSviNXMD1ZqdFO6B9Vl8O6X.7ZIauNm34bpUCWnDg91C9OgcnQ/7XZh7rCt1JPQfc/g/vpRdWTqbp0/'
}

// Compute expected hash for a password
function hashFor (password) {
return `{SHA512-CRYPT}${crypt(password, SALT)}`
}

/**
* Returns an object that will be filled with event-counts from the PostfixAccounts object
* @param {PostfixAccounts} postfixAccounts
Expand All @@ -52,19 +32,12 @@ function eventCounter (postfixAccounts) {
return eventLog
}

describe('postfix-accounts:', function () {
let originalSHA512Salter

// mock "createSalt" to get deterministic hashes
beforeEach(async () => {
await cpr('test/fixtures', 'test-tmp/fixtures', {deleteFirst: true})
originalSHA512Salter = crypt.createSalt.salters['sha512']
crypt.createSalt.salters['sha512'] = () => SALT
})
const fixtureOnlyRailtest = {
'railtest@test.knappi.org': fixture['railtest@test.knappi.org']
}

afterEach(() => {
crypt.createSalt.salters['sha512'] = originalSHA512Salter
})
describe('postfix-accounts:', function () {
mochaSetup()

describe('the static method #createPasswordHash and #verifyPassword', function () {
it('should create a password hash from a password in the dovecot form ({SHA-512}$6$...', async function () {
Expand All @@ -83,8 +56,8 @@ describe('postfix-accounts:', function () {
})

it('should not verify a password hash created by doveadm', async function () {
let doveHash = '{SHA512-CRYPT}$6$fIGcYDOu6Acq361c$Xj/DuVClJrKEFWWu4irr8So6GwqwGdSbiMU3tG.RlM/4hoQMIIsqHry21zqmd/McsAVeH5/meBL1kc8wWBfwJ.'
expect(await PostfixAccounts.verifyPassword('abc', doveHash)).to.be.true()
let dovecotHash = '{SHA512-CRYPT}$6$fIGcYDOu6Acq361c$Xj/DuVClJrKEFWWu4irr8So6GwqwGdSbiMU3tG.RlM/4hoQMIIsqHry21zqmd/McsAVeH5/meBL1kc8wWBfwJ.'
expect(await PostfixAccounts.verifyPassword('abc', dovecotHash)).to.be.true()
})
})

Expand Down
102 changes: 102 additions & 0 deletions test/router-spec.js
@@ -0,0 +1,102 @@
/*!
* docker-mailserver-management <https://github.com/nknapp/docker-mailserver-management>
*
* Copyright (c) 2017 Nils Knappmeier.
* Released under the MIT license.
*/

/* eslint-env mocha */

const {PostfixAccounts} = require('../src/postfix-accounts')

const express = require('express')
const {createRouter} = require('../src/router')
const http = require('http')
const popsicle = require('popsicle')

const chai = require('chai')
chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised'))
const expect = chai.expect
const {mochaSetup, hashFor, fixture} = require('./_utils')

describe('the router:', function () {

var server
var baseUrl
var postfixAccounts
before(async () => {
postfixAccounts = await PostfixAccounts.load('test-tmp/fixtures/postfix-accounts.cf')

var app = express()
app.use(createRouter(postfixAccounts))
server = http.createServer(app)
return new Promise((resolve, reject) => {
server.listen(function (err) {
baseUrl = `http://localhost:${server.address().port}`
return err ? reject(err) : resolve()
})
})
})

after(() => {
return new Promise((resolve, reject) => {
server.close((err) => err ? reject(err) : resolve())
})
})

// Setup tmp directory and salt
mochaSetup()

beforeEach(() => postfixAccounts.reload())

// invoke the rest-resource
function verifyAndUpdate (username, oldPassword, newPassword) {
return popsicle
.post({url: `${baseUrl}/user/${encodeURIComponent(username)}`, body: {oldPassword, newPassword}})
.use(popsicle.plugins.parse(['json']))
}

describe('The /user/:username resource', function () {
it('should write the new password if the old password is correct', async function () {
const response = await verifyAndUpdate('mailtest@test.knappi.org', 'abc', 'ab')

// Checking response
expect(response.status).to.equal(200)
expect(response.body).to.deep.equal({'success': true, 'username': 'mailtest@test.knappi.org'})
// Checking modification of accounts object
expect(postfixAccounts.accounts).to.deep.equal({
'mailtest@test.knappi.org': hashFor('ab'),
'railtest@test.knappi.org': fixture['railtest@test.knappi.org']
})
})

it('should return 403 if the old password is incorrect', async function () {
const response = await verifyAndUpdate('mailtest@test.knappi.org', 'badPassword', 'ab')

// Checking response
expect(response.status).to.equal(403)
expect(response.body).to.deep.equal({
'success': false,
'code': 403,
'message': 'Bad username or password'
})
// Checking non-modification of accounts object
expect(postfixAccounts.accounts).to.deep.equal(fixture)
})

it('should return 400 if now old password is specified', async function () {
const response = await verifyAndUpdate('mailtest@test.knappi.org')

// Checking response
expect(response.status).to.equal(400)
expect(response.body).to.deep.equal({
'success': false,
'code': 400,
'message': 'Request must contain username, oldPassword and newPassword'
})
// Checking non-modification of accounts object
expect(postfixAccounts.accounts).to.deep.equal(fixture)
})
})
})

0 comments on commit 5f5b52f

Please sign in to comment.