Skip to content

Commit 89a0de4

Browse files
authored
Merge pull request #1515 from hackmdio/release-2.1.0
2 parents e29422f + 720348a commit 89a0de4

File tree

22 files changed

+1851
-834
lines changed

22 files changed

+1851
-834
lines changed

Diff for: .travis.yml

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
language: node_js
22

33
node_js:
4-
- "lts/carbon"
54
- "lts/dubnium"
65
- "11"
76
- "12"
@@ -12,7 +11,6 @@ cache: npm
1211
matrix:
1312
fast_finish: true
1413
include:
15-
- node_js: lts/carbon
1614
- node_js: lts/dubnium
1715
allow_failures:
1816
- node_js: "11"

Diff for: app.js

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var passportSocketIo = require('passport.socketio')
1717
var helmet = require('helmet')
1818
var i18n = require('i18n')
1919
var flash = require('connect-flash')
20+
var apiMetrics = require('prometheus-api-metrics')
2021

2122
// core
2223
var config = require('./lib/config')
@@ -56,6 +57,12 @@ function createHttpServer () {
5657
var app = express()
5758
var server = createHttpServer()
5859

60+
// API and process monitoring with Prometheus for Node.js micro-service
61+
app.use(apiMetrics({
62+
metricsPath: '/metrics/router',
63+
excludeRoutes: ['/metrics/codimd']
64+
}))
65+
5966
// logger
6067
app.use(morgan('combined', {
6168
stream: logger.stream
@@ -131,6 +138,7 @@ app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.st
131138
app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), { maxAge: config.staticCacheTime }))
132139
app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), { maxAge: config.staticCacheTime }))
133140
app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), { maxAge: config.staticCacheTime }))
141+
app.use(require('./lib/metrics').router)
134142

135143
// session
136144
app.use(session({

Diff for: deployments/Dockerfile

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
FROM hackmdio/buildpack:1.0.4 as BUILD
1+
ARG RUNTIME
2+
3+
FROM hackmdio/buildpack:node-10-0baafb79 as BUILD
24

35
COPY --chown=hackmd:hackmd . .
46

@@ -12,11 +14,12 @@ RUN set -xe && \
1214
rm -rf .git .gitignore .travis.yml .dockerignore .editorconfig .babelrc .mailmap .sequelizerc.example \
1315
test docs contribute \
1416
package-lock.json webpack.prod.js webpack.htmlexport.js webpack.dev.js webpack.common.js \
15-
config.json.example README.md CONTRIBUTING.md AUTHORS
17+
config.json.example README.md CONTRIBUTING.md AUTHORS node_modules
1618

17-
FROM hackmdio/runtime:1.0.6
19+
FROM $RUNTIME
1820
USER hackmd
1921
WORKDIR /home/hackmd/app
2022
COPY --chown=1500:1500 --from=BUILD /home/hackmd/app .
23+
RUN npm install --production && npm cache clean --force && rm -rf /tmp/{core-js-banners,phantomjs}
2124
EXPOSE 3000
2225
ENTRYPOINT ["/home/hackmd/app/docker-entrypoint.sh"]

Diff for: deployments/build.sh

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
#!/usr/bin/env bash
22

3+
set -euo pipefail
4+
set -x
5+
36
CURRENT_DIR=$(dirname "$BASH_SOURCE")
47

5-
docker build -t hackmdio/codimd -f "$CURRENT_DIR/Dockerfile" "$CURRENT_DIR/.."
8+
GIT_SHA1="$(git rev-parse HEAD)"
9+
GIT_SHORT_ID="${GIT_SHA1:0:8}"
10+
GIT_TAG=$(git describe --exact-match --tags $(git log -n1 --pretty='%h') 2>/dev/null || echo "")
11+
12+
DOCKER_TAG="${GIT_TAG:-$GIT_SHORT_ID}"
13+
14+
docker build --build-arg RUNTIME=hackmdio/runtime:node-10-d27854ef -t "hackmdio/hackmd:$DOCKER_TAG" -f "$CURRENT_DIR/Dockerfile" "$CURRENT_DIR/.."
15+
16+
docker build --build-arg RUNTIME=hackmdio/runtime:node-10-cjk-d27854ef -t "hackmdio/hackmd:$DOCKER_TAG-cjk" -f "$CURRENT_DIR/Dockerfile" "$CURRENT_DIR/.."

Diff for: lib/auth/email/index.js

+34-28
Original file line numberDiff line numberDiff line change
@@ -15,50 +15,56 @@ const emailAuth = module.exports = Router()
1515

1616
passport.use(new LocalStrategy({
1717
usernameField: 'email'
18-
}, function (email, password, done) {
18+
}, async function (email, password, done) {
1919
if (!validator.isEmail(email)) return done(null, false)
20-
models.User.findOne({
21-
where: {
22-
email: email
23-
}
24-
}).then(function (user) {
20+
21+
try {
22+
const user = await models.User.findOne({
23+
where: {
24+
email: email
25+
}
26+
})
27+
2528
if (!user) return done(null, false)
26-
if (!user.verifyPassword(password)) return done(null, false)
29+
if (!await user.verifyPassword(password)) return done(null, false)
2730
return done(null, user)
28-
}).catch(function (err) {
31+
} catch (err) {
2932
logger.error(err)
3033
return done(err)
31-
})
34+
}
3235
}))
3336

3437
if (config.allowEmailRegister) {
35-
emailAuth.post('/register', urlencodedParser, function (req, res, next) {
38+
emailAuth.post('/register', urlencodedParser, async function (req, res, next) {
3639
if (!req.body.email || !req.body.password) return response.errorBadRequest(req, res)
3740
if (!validator.isEmail(req.body.email)) return response.errorBadRequest(req, res)
38-
models.User.findOrCreate({
39-
where: {
40-
email: req.body.email
41-
},
42-
defaults: {
43-
password: req.body.password
44-
}
45-
}).spread(function (user, created) {
46-
if (user) {
47-
if (created) {
48-
logger.debug('user registered: ' + user.id)
49-
req.flash('info', "You've successfully registered, please signin.")
50-
} else {
51-
logger.debug('user found: ' + user.id)
52-
req.flash('error', 'This email has been used, please try another one.')
41+
try {
42+
const [user, created] = await models.User.findOrCreate({
43+
where: {
44+
email: req.body.email
45+
},
46+
defaults: {
47+
password: req.body.password
5348
}
49+
})
50+
51+
if (!user) {
52+
req.flash('error', 'Failed to register your account, please try again.')
5453
return res.redirect(config.serverURL + '/')
5554
}
56-
req.flash('error', 'Failed to register your account, please try again.')
55+
56+
if (created) {
57+
logger.debug('user registered: ' + user.id)
58+
req.flash('info', "You've successfully registered, please signin.")
59+
} else {
60+
logger.debug('user found: ' + user.id)
61+
req.flash('error', 'This email has been used, please try another one.')
62+
}
5763
return res.redirect(config.serverURL + '/')
58-
}).catch(function (err) {
64+
} catch (err) {
5965
logger.error('auth callback failed: ' + err)
6066
return response.errorInternalError(req, res)
61-
})
67+
}
6268
})
6369
}
6470

Diff for: lib/auth/oauth2/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ passport.use(new OAuth2CustomStrategy({
1616
clientSecret: config.oauth2.clientSecret,
1717
callbackURL: config.serverURL + '/auth/oauth2/callback',
1818
userProfileURL: config.oauth2.userProfileURL,
19+
state: config.oauth2.state,
1920
scope: config.oauth2.scope
2021
}, passportGeneralCallback))
2122

Diff for: lib/config/default.js

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ module.exports = {
100100
userProfileDisplayNameAttr: 'displayName',
101101
userProfileEmailAttr: 'email',
102102
userProfilePhotoAttr: 'photo',
103+
state: true,
103104
scope: 'email'
104105
},
105106
facebook: {

Diff for: lib/config/environment.js

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ module.exports = {
9494
tokenURL: process.env.CMD_OAUTH2_TOKEN_URL,
9595
userProfileURL: process.env.CMD_OAUTH2_USER_PROFILE_URL,
9696
scope: process.env.CMD_OAUTH2_SCOPE,
97+
state: process.env.CMD_OAUTH2_STATE,
9798
userProfileUsernameAttr: process.env.CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR,
9899
userProfileDisplayNameAttr: process.env.CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR,
99100
userProfileEmailAttr: process.env.CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR,

Diff for: lib/imageRouter/lutim.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const config = require('../config')
33
const logger = require('../logger')
44

5-
const lutim = require('lib/imageRouter/lutim')
5+
const lutim = require('lutim')
66

77
exports.uploadImage = function (imagePath, callback) {
88
if (!imagePath || typeof imagePath !== 'string') {

Diff for: lib/imageRouter/s3.js

+10-9
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ const config = require('../config')
66
const { getImageMimeType } = require('../utils')
77
const logger = require('../logger')
88

9-
const AWS = require('aws-sdk')
10-
const awsConfig = new AWS.Config(config.s3)
11-
const s3 = new AWS.S3(awsConfig)
9+
const { S3Client } = require('@aws-sdk/client-s3-node/S3Client')
10+
const { PutObjectCommand } = require('@aws-sdk/client-s3-node/commands/PutObjectCommand')
11+
12+
const s3 = new S3Client(config.s3)
1213

1314
exports.uploadImage = function (imagePath, callback) {
1415
if (!imagePath || typeof imagePath !== 'string') {
@@ -32,23 +33,23 @@ exports.uploadImage = function (imagePath, callback) {
3233
Body: buffer,
3334
ACL: 'public-read'
3435
}
35-
3636
const mimeType = getImageMimeType(imagePath)
3737
if (mimeType) { params.ContentType = mimeType }
3838

39-
s3.putObject(params, function (err, data) {
40-
if (err) {
41-
callback(new Error(err), null)
42-
return
43-
}
39+
const command = new PutObjectCommand(params)
4440

41+
s3.send(command).then(data => {
4542
let s3Endpoint = 's3.amazonaws.com'
4643
if (config.s3.endpoint) {
4744
s3Endpoint = config.s3.endpoint
4845
} else if (config.s3.region && config.s3.region !== 'us-east-1') {
4946
s3Endpoint = `s3-${config.s3.region}.amazonaws.com`
5047
}
5148
callback(null, `https://${s3Endpoint}/${config.s3bucket}/${params.Key}`)
49+
}).catch(err => {
50+
if (err) {
51+
callback(new Error(err), null)
52+
}
5253
})
5354
})
5455
}

Diff for: lib/metrics.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
const { Router } = require('express')
4+
5+
const { wrap } = require('./utils')
6+
7+
// load controller
8+
const statusController = require('./status')
9+
const appRouter = Router()
10+
11+
// register route
12+
appRouter.get('/status', wrap(statusController.getStatus))
13+
appRouter.get('/metrics/codimd', wrap(statusController.getMetrics))
14+
15+
exports.router = appRouter

Diff for: lib/models/user.js

+27-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22
// external modules
33
var Sequelize = require('sequelize')
4-
var scrypt = require('scrypt')
4+
var Scrypt = require('scrypt-kdf')
55

66
// core
77
var logger = require('../logger')
@@ -41,22 +41,34 @@ module.exports = function (sequelize, DataTypes) {
4141
}
4242
},
4343
password: {
44-
type: Sequelize.TEXT,
45-
set: function (value) {
46-
var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString('hex')
47-
this.setDataValue('password', hash)
48-
}
44+
type: Sequelize.TEXT
4945
}
5046
})
5147

52-
User.prototype.verifyPassword = function (attempt) {
53-
if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) {
48+
User.hashPassword = async function (plain) {
49+
return (await Scrypt.kdf(plain, await Scrypt.pickParams(0.1))).toString('hex')
50+
}
51+
52+
User.prototype.verifyPassword = async function (attempt) {
53+
if (await Scrypt.verify(Buffer.from(this.password, 'hex'), attempt)) {
5454
return this
55-
} else {
56-
return false
5755
}
56+
57+
return false
5858
}
5959

60+
User.addHook('beforeCreate', async function (user) {
61+
// only do hash when password is presented
62+
if (user.password) {
63+
user.password = await User.hashPassword(user.password)
64+
}
65+
})
66+
User.addHook('beforeUpdate', async function (user) {
67+
if (user.changed('password')) {
68+
user.password = await User.hashPassword(user.password)
69+
}
70+
})
71+
6072
User.associate = function (models) {
6173
User.hasMany(models.Note, {
6274
foreignKey: 'ownerId',
@@ -103,10 +115,11 @@ module.exports = function (sequelize, DataTypes) {
103115
else photo += '?size=bigger'
104116
break
105117
case 'github':
106-
if (profile.photos && profile.photos[0]) photo = profile.photos[0].value.replace('?', '')
107-
else photo = 'https://avatars.githubusercontent.com/u/' + profile.id
108-
if (bigger) photo += '?s=400'
109-
else photo += '?s=96'
118+
const photoURL = new URL(profile.photos && profile.photos[0]
119+
? profile.photos[0].value
120+
: `https://avatars.githubusercontent.com/u/${profile.id}`)
121+
photoURL.searchParams.set('s', bigger ? 400 : 96)
122+
photo = photoURL.toString()
110123
break
111124
case 'gitlab':
112125
photo = profile.avatarUrl

Diff for: lib/note/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ async function showNote (req, res) {
6161
// if allow free url enable, auto create note
6262
if (!config.allowFreeURL || config.forbiddenNoteIDs.includes(noteId)) {
6363
return errorNotFound(req, res)
64+
} else if (!config.allowAnonymous && !userId) {
65+
return errorForbidden(req, res)
6466
}
6567
note = await createNote(userId, noteId)
6668
}

Diff for: lib/realtime/realtime.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,12 @@ function getStatus () {
298298
}
299299
})
300300
.catch(function (err) {
301-
return logger.error('count user failed: ' + err)
301+
logger.error('count user failed: ' + err)
302+
return Promise.reject(new Error('count user failed: ' + err))
302303
})
303304
}).catch(function (err) {
304-
return logger.error('count note failed: ' + err)
305+
logger.error('count note failed: ' + err)
306+
return Promise.reject(new Error('count note failed: ' + err))
305307
})
306308
}
307309

@@ -772,8 +774,7 @@ function queueForConnect (socket) {
772774
const noteId = socket.noteId
773775
logger.info('SERVER connected a client to [' + noteId + ']:')
774776
logger.info(JSON.stringify(user))
775-
// logger.info(notes);
776-
getStatus(function (data) {
777+
getStatus().then(function (data) {
777778
logger.info(JSON.stringify(data))
778779
})
779780
}

Diff for: lib/routes.js

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ appRouter.get('/404', errorPageController.errorNotFound)
2727
// get 500 internal error
2828
appRouter.get('/500', errorPageController.errorInternalError)
2929

30-
appRouter.get('/status', wrap(statusController.getStatus))
3130
appRouter.get('/config', statusController.getConfig)
3231

3332
// register auth module

0 commit comments

Comments
 (0)