Merge branch 'feature/drop-node-6'
This commit is contained in:
commit
4c90863f2c
7 changed files with 134 additions and 48 deletions
2
.babelrc
2
.babelrc
|
@ -2,7 +2,7 @@
|
||||||
"presets": [
|
"presets": [
|
||||||
["env", {
|
["env", {
|
||||||
"targets": {
|
"targets": {
|
||||||
"node": "6",
|
"node": "8",
|
||||||
"uglify": true
|
"uglify": true
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
48
.travis.yml
48
.travis.yml
|
@ -1,40 +1,40 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
dist: trusty
|
dist: xenial
|
||||||
cache: yarn
|
cache: yarn
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- CXX=g++-4.8
|
|
||||||
- YARN_VERSION=1.15.2
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- env: task=npm-test
|
- stage: Static Tests
|
||||||
node_js:
|
name: eslint
|
||||||
- 6
|
|
||||||
before_install:
|
|
||||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
|
|
||||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
|
||||||
- env: task=npm-test
|
|
||||||
node_js:
|
|
||||||
- 8
|
|
||||||
before_install:
|
|
||||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
|
|
||||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
|
||||||
- env: task=npm-test
|
|
||||||
node_js:
|
node_js:
|
||||||
- 10
|
- 10
|
||||||
before_install:
|
script:
|
||||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
|
- yarn run eslint
|
||||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
- name: ShellCheck
|
||||||
- env: task=ShellCheck
|
|
||||||
script:
|
script:
|
||||||
- shellcheck bin/heroku bin/setup
|
- shellcheck bin/heroku bin/setup
|
||||||
language: generic
|
language: generic
|
||||||
- env: task=json-lint
|
- name: json-lint
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
- jq
|
- jq
|
||||||
script:
|
script:
|
||||||
- npm run jsonlint
|
- yarn run jsonlint
|
||||||
language: generic
|
language: generic
|
||||||
|
- stage: Dynamic Tests
|
||||||
|
name: Node.js 8
|
||||||
|
node_js:
|
||||||
|
- 8
|
||||||
|
script:
|
||||||
|
- yarn run mocha-suite
|
||||||
|
- name: Node.js 10
|
||||||
|
node_js:
|
||||||
|
- 10
|
||||||
|
script:
|
||||||
|
- yarn run mocha-suite
|
||||||
|
- name: Node.js 12
|
||||||
|
node_js:
|
||||||
|
- 12
|
||||||
|
script:
|
||||||
|
- yarn run mocha-suite
|
||||||
|
|
|
@ -3,11 +3,10 @@ Manual Installation
|
||||||
|
|
||||||
## Requirements on your server
|
## Requirements on your server
|
||||||
|
|
||||||
- Node.js 6.x or up (test up to 7.5.0) and <10.x
|
- Node.js 8.5 or up
|
||||||
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8`
|
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8`
|
||||||
- npm (and its dependencies, [node-gyp](https://github.com/nodejs/node-gyp#installation))
|
- npm (and its dependencies, [node-gyp](https://github.com/nodejs/node-gyp#installation))
|
||||||
- yarn
|
- yarn
|
||||||
- `libssl-dev` for building scrypt (see [here](https://github.com/ml1nk/node-scrypt/blob/master/README.md#installation-instructions) for further information)
|
|
||||||
- Bash (for the setup script)
|
- Bash (for the setup script)
|
||||||
- For **building** CodiMD we recommend to use a machine with at least **2GB** RAM
|
- For **building** CodiMD we recommend to use a machine with at least **2GB** RAM
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
// external modules
|
// external modules
|
||||||
var Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
var scrypt = require('@mlink/scrypt')
|
const crypto = require('crypto')
|
||||||
|
if (!crypto.scrypt) {
|
||||||
|
// polyfill for node.js 8.0, see https://github.com/chrisveness/scrypt-kdf#openssl-implementation
|
||||||
|
const scryptAsync = require('scrypt-async')
|
||||||
|
crypto.scrypt = function (password, salt, keylen, options, callback) {
|
||||||
|
const opt = Object.assign({}, options, { dkLen: keylen })
|
||||||
|
scryptAsync(password, salt, opt, (derivedKey) => callback(null, Buffer.from(derivedKey)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scrypt = require('scrypt-kdf')
|
||||||
|
|
||||||
// core
|
// core
|
||||||
var logger = require('../logger')
|
const logger = require('../logger')
|
||||||
var {generateAvatarURL} = require('../letter-avatars')
|
const { generateAvatarURL } = require('../letter-avatars')
|
||||||
|
|
||||||
module.exports = function (sequelize, DataTypes) {
|
module.exports = function (sequelize, DataTypes) {
|
||||||
var User = sequelize.define('User', {
|
var User = sequelize.define('User', {
|
||||||
|
@ -41,20 +50,12 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
type: Sequelize.TEXT,
|
type: Sequelize.TEXT
|
||||||
set: function (value) {
|
|
||||||
var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString('hex')
|
|
||||||
this.setDataValue('password', hash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
instanceMethods: {
|
instanceMethods: {
|
||||||
verifyPassword: function (attempt) {
|
verifyPassword: function (attempt) {
|
||||||
if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) {
|
return scrypt.verify(Buffer.from(this.password, 'hex'), attempt)
|
||||||
return this
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
classMethods: {
|
classMethods: {
|
||||||
|
@ -153,5 +154,19 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function updatePasswordHashHook (user, options, done) {
|
||||||
|
// suggested way to hash passwords to be able to do this asynchronously:
|
||||||
|
// @see https://github.com/sequelize/sequelize/issues/1821#issuecomment-44265819
|
||||||
|
if (!user.changed('password')) { return done() }
|
||||||
|
|
||||||
|
scrypt.kdf(user.getDataValue('password'), { logN: 15 }).then(keyBuf => {
|
||||||
|
user.setDataValue('password', keyBuf.toString('hex'))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
User.beforeCreate(updatePasswordHashHook)
|
||||||
|
User.beforeUpdate(updatePasswordHashHook)
|
||||||
|
|
||||||
return User
|
return User
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,14 @@ passport.use(new LocalStrategy({
|
||||||
}
|
}
|
||||||
}).then(function (user) {
|
}).then(function (user) {
|
||||||
if (!user) return done(null, false)
|
if (!user) return done(null, false)
|
||||||
if (!user.verifyPassword(password)) return done(null, false)
|
user.verifyPassword(password).then(verified => {
|
||||||
|
if (verified) {
|
||||||
return done(null, user)
|
return done(null, user)
|
||||||
|
} else {
|
||||||
|
logger.warn('invalid password given for %s', user.email)
|
||||||
|
return done(null, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return done(err)
|
return done(err)
|
||||||
|
|
12
package.json
12
package.json
|
@ -5,9 +5,10 @@
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run-script eslint && npm run-script jsonlint && mocha",
|
"test": "npm run-script eslint && npm run-script jsonlint && npm run-script mocha-suite",
|
||||||
"eslint": "node_modules/.bin/eslint lib public test app.js",
|
"eslint": "node_modules/.bin/eslint lib public test app.js",
|
||||||
"jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' -o -type f -name '*.json.example' | while read json; do echo $json ; jq . $json; done",
|
"jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' -o -type f -name '*.json.example' | while read json; do echo $json ; jq . $json; done",
|
||||||
|
"mocha-suite": "NODE_ENV=test CMD_DB_URL=\"sqlite::memory:\" mocha --exit",
|
||||||
"standard": "echo 'standard is no longer being used, use `npm run eslint` instead!' && exit 1",
|
"standard": "echo 'standard is no longer being used, use `npm run eslint` instead!' && exit 1",
|
||||||
"dev": "webpack --config webpack.dev.js --progress --colors --watch",
|
"dev": "webpack --config webpack.dev.js --progress --colors --watch",
|
||||||
"heroku-prebuild": "bin/heroku",
|
"heroku-prebuild": "bin/heroku",
|
||||||
|
@ -57,7 +58,6 @@
|
||||||
"jquery-ui": "^1.12.1",
|
"jquery-ui": "^1.12.1",
|
||||||
"js-cookie": "^2.1.3",
|
"js-cookie": "^2.1.3",
|
||||||
"js-sequence-diagrams": "git+https://github.com/codimd/js-sequence-diagrams.git",
|
"js-sequence-diagrams": "git+https://github.com/codimd/js-sequence-diagrams.git",
|
||||||
"wurl": "^2.5.3",
|
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"jsdom-nogyp": "^0.8.3",
|
"jsdom-nogyp": "^0.8.3",
|
||||||
"keymaster": "^1.6.2",
|
"keymaster": "^1.6.2",
|
||||||
|
@ -110,7 +110,8 @@
|
||||||
"readline-sync": "^1.4.7",
|
"readline-sync": "^1.4.7",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"reveal.js": "~3.7.0",
|
"reveal.js": "~3.7.0",
|
||||||
"@mlink/scrypt": "^6.1.2",
|
"scrypt-async": "^2.0.1",
|
||||||
|
"scrypt-kdf": "^2.0.1",
|
||||||
"select2": "^3.5.2-browserify",
|
"select2": "^3.5.2-browserify",
|
||||||
"sequelize": "^3.28.0",
|
"sequelize": "^3.28.0",
|
||||||
"sequelize-cli": "^2.5.1",
|
"sequelize-cli": "^2.5.1",
|
||||||
|
@ -118,7 +119,7 @@
|
||||||
"socket.io": "~2.1.1",
|
"socket.io": "~2.1.1",
|
||||||
"socket.io-client": "~2.1.1",
|
"socket.io-client": "~2.1.1",
|
||||||
"spin.js": "^2.3.2",
|
"spin.js": "^2.3.2",
|
||||||
"sqlite3": "^4.0.1",
|
"sqlite3": "^4.0.7",
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"string": "^3.3.3",
|
"string": "^3.3.3",
|
||||||
"tedious": "^1.14.0",
|
"tedious": "^1.14.0",
|
||||||
|
@ -131,6 +132,7 @@
|
||||||
"viz.js": "^1.7.0",
|
"viz.js": "^1.7.0",
|
||||||
"winston": "^3.1.0",
|
"winston": "^3.1.0",
|
||||||
"ws": "^6.0.0",
|
"ws": "^6.0.0",
|
||||||
|
"wurl": "^2.5.3",
|
||||||
"xss": "^1.0.3"
|
"xss": "^1.0.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
@ -139,7 +141,7 @@
|
||||||
"**/request": "^2.88.0"
|
"**/request": "^2.88.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.x"
|
"node": ">=8.x"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/codimd/server/issues",
|
"bugs": "https://github.com/codimd/server/issues",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
64
test/user.js
Normal file
64
test/user.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/* eslint-env node, mocha */
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const assert = require('assert')
|
||||||
|
|
||||||
|
const models = require('../lib/models')
|
||||||
|
const User = models.User
|
||||||
|
|
||||||
|
describe('User Sequelize model', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
return models.sequelize.sync({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stores a password hash on creation and verifies that password', function () {
|
||||||
|
const userData = {
|
||||||
|
password: 'test123'
|
||||||
|
}
|
||||||
|
const intentionallyInvalidPassword = 'stuff'
|
||||||
|
|
||||||
|
return User.create(userData).then(u => {
|
||||||
|
return Promise.all([
|
||||||
|
u.verifyPassword(userData.password).then(result => assert.strictEqual(result, true)),
|
||||||
|
u.verifyPassword(intentionallyInvalidPassword).then(result => assert.strictEqual(result, false))
|
||||||
|
]).catch(e => assert.fail(e))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can cope with password stored in standard scrypt header format', function () {
|
||||||
|
const testKey = '736372797074000e00000008000000018c7b8c1ac273fd339badde759b3efc418bc61b776debd02dfe95989383cf9980ad21d2403dce33f4b551f5e98ce84edb792aee62600b1303ab8d4e6f0a53b0746e73193dbf557b888efc83a2d6a055a9'
|
||||||
|
const validPassword = 'test'
|
||||||
|
const intentionallyInvalidPassword = 'stuff'
|
||||||
|
|
||||||
|
const u = User.build()
|
||||||
|
u.setDataValue('password', testKey) // this circumvents the setter - which we don't need in this case!
|
||||||
|
return Promise.all([
|
||||||
|
u.verifyPassword(validPassword).then(result => assert.strictEqual(result, true)),
|
||||||
|
u.verifyPassword(intentionallyInvalidPassword).then(result => assert.strictEqual(result, false))
|
||||||
|
]).catch(e => assert.fail(e))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deals with various characters correctly', function () {
|
||||||
|
const combinations = [
|
||||||
|
// ['correct password', 'scrypt syle hash']
|
||||||
|
['test', '736372797074000e00000008000000018c7b8c1ac273fd339badde759b3efc418bc61b776debd02dfe95989383cf9980ad21d2403dce33f4b551f5e98ce84edb792aee62600b1303ab8d4e6f0a53b0746e73193dbf557b888efc83a2d6a055a9'],
|
||||||
|
['ohai', '736372797074000e00000008000000010efec4e5ce6a5294491f1b1cccc38d3562f84844b9271aef635f8bc338cf4e0e0bac62ebb11379e85894c1f694e038fc39b087b4fdacd1280b50a7382d7ffbfc82f2190bef70d47708d2a94b75126294'],
|
||||||
|
['my secret pw', '736372797074000f0000000800000001ffb4cd10a1dfe9e64c1e5416fd6d55b390b6822e78b46fd1f963fe9f317a1e05f9c5fee15e1f618286f4e38b55364ae1e7dc295c9dc33ee0f5712e86afe37e5784ff9c7cf84cf0e631dd11f84f3621e7'],
|
||||||
|
['my secret pw', /* different hash! */ '736372797074000f0000000800000001f6083e9593365acd07550f7c72f19973fb7d52c3ef0a78026ff66c48ab14493843c642167b5e6b7f31927e8eeb912bc2639e41955fae15da5099998948cfeacd022f705624931c3b30104e6bb296b805'],
|
||||||
|
['i am so extremely long, it\'s not even funny. Wait, you\'re still reading?', '736372797074000f00000008000000012d205f7bb529bb3a8b8bb25f5ab46197c7e9baf1aad64cf5e7b2584c84748cacf5e60631d58d21cb51fa34ea93b517e2fe2eb722931db5a70ff5a1330d821288ee7380c4136369f064b71b191a785a5b']
|
||||||
|
]
|
||||||
|
const intentionallyInvalidPassword = 'stuff'
|
||||||
|
|
||||||
|
return Promise.all(combinations.map((combination, index) => {
|
||||||
|
const u = User.build()
|
||||||
|
u.setDataValue('password', combination[1])
|
||||||
|
return Promise.all([
|
||||||
|
u.verifyPassword(combination[0])
|
||||||
|
.then(result => assert.strictEqual(result, true, `password #${index} "${combination[0]}" should have been verified`)),
|
||||||
|
u.verifyPassword(intentionallyInvalidPassword)
|
||||||
|
.then(result => assert.strictEqual(result, false, `password #${index} "${combination[0]}" should NOT have been verified`))
|
||||||
|
])
|
||||||
|
})).catch(e => assert.fail(e))
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue