hedgedoc/lib/history.js
Erik Michelson eb2a1d8344
refactor: use user-token for historyDelete too
Previously, the user token was only used for the endpoint
to delete the user itself. This commit adds that token to
the history deletion as well.

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
2024-12-17 14:51:46 +01:00

205 lines
5.8 KiB
JavaScript

'use strict'
// history
// external modules
const LZString = require('lz-string')
// core
const logger = require('./logger')
const models = require('./models')
const errors = require('./errors')
// public
const History = {
historyGet,
historyPost,
historyDelete,
updateHistory
}
function getHistory (userid, callback) {
models.User.findOne({
where: {
id: userid
}
}).then(function (user) {
if (!user) {
return callback(null, null)
}
let history = {}
if (user.history) {
history = JSON.parse(user.history)
// migrate LZString encoded note id to base64url encoded note id
for (let i = 0, l = history.length; i < l; i++) {
// Calculate minimal string length for an UUID that is encoded
// base64 encoded and optimize comparsion by using -1
// this should make a lot of LZ-String parsing errors obsolete
// as we can assume that a nodeId that is 48 chars or longer is a
// noteID.
const base64UuidLength = ((4 * 36) / 3) - 1
if (!(history[i].id.length > base64UuidLength)) {
continue
}
try {
const id = LZString.decompressFromBase64(history[i].id)
if (id && models.Note.checkNoteIdValid(id)) {
history[i].id = models.Note.encodeNoteId(id)
}
} catch (err) {
// most error here comes from LZString, ignore
if (err.message === 'Cannot read property \'charAt\' of undefined') {
logger.warning('Looks like we can not decode "' + history[i].id + '" with LZString. Can be ignored.')
} else {
logger.error(err)
}
}
}
history = parseHistoryToObject(history)
}
logger.debug(`read history success: ${user.id}`)
return callback(null, history)
}).catch(function (err) {
logger.error('read history failed: ' + err)
return callback(err, null)
})
}
function setHistory (userid, history, callback) {
models.User.update({
history: JSON.stringify(parseHistoryToArray(history))
}, {
where: {
id: userid
}
}).then(function (count) {
return callback(null, count)
}).catch(function (err) {
logger.error('set history failed: ' + err)
return callback(err, null)
})
}
function updateHistory (userid, noteId, document, time) {
if (userid && noteId && typeof document !== 'undefined') {
getHistory(userid, function (err, history) {
if (err || !history) return
if (!history[noteId]) {
history[noteId] = {}
}
const noteHistory = history[noteId]
const noteInfo = models.Note.parseNoteInfo(document)
noteHistory.id = noteId
noteHistory.text = noteInfo.title
noteHistory.time = time || Date.now()
noteHistory.tags = noteInfo.tags
setHistory(userid, history, function (err, count) {
if (err) {
logger.log(err)
}
})
})
}
}
function parseHistoryToArray (history) {
const _history = []
Object.keys(history).forEach(function (key) {
const item = history[key]
_history.push(item)
})
return _history
}
function parseHistoryToObject (history) {
const _history = {}
for (let i = 0, l = history.length; i < l; i++) {
const item = history[i]
_history[item.id] = item
}
return _history
}
function historyGet (req, res) {
if (req.isAuthenticated()) {
getHistory(req.user.id, function (err, history) {
if (err) return errors.errorInternalError(res)
if (!history) return errors.errorNotFound(res)
res.send({
history: parseHistoryToArray(history)
})
})
} else {
return errors.errorForbidden(res)
}
}
function historyPost (req, res) {
if (req.isAuthenticated()) {
const noteId = req.params.noteId
if (!noteId) {
if (typeof req.body.history === 'undefined') return errors.errorBadRequest(res)
logger.debug(`SERVER received history from [${req.user.id}]: ${req.body.history}`)
try {
const history = JSON.parse(req.body.history)
if (Array.isArray(history)) {
setHistory(req.user.id, history, function (err, count) {
if (err) return errors.errorInternalError(res)
res.end()
})
} else {
return errors.errorBadRequest(res)
}
} catch (err) {
return errors.errorBadRequest(res)
}
} else {
if (typeof req.body.pinned === 'undefined') return errors.errorBadRequest(res)
getHistory(req.user.id, function (err, history) {
if (err) return errors.errorInternalError(res)
if (!history) return errors.errorNotFound(res)
if (!history[noteId]) return errors.errorNotFound(res)
if (req.body.pinned === 'true' || req.body.pinned === 'false') {
history[noteId].pinned = (req.body.pinned === 'true')
setHistory(req.user.id, history, function (err, count) {
if (err) return errors.errorInternalError(res)
res.end()
})
} else {
return errors.errorBadRequest(res)
}
})
}
} else {
return errors.errorForbidden(res)
}
}
function historyDelete (req, res) {
if (!req.isAuthenticated()) {
return errors.errorForbidden(res)
}
const token = req.query.token
if (!token || token !== req.user.deleteToken) {
return errors.errorForbidden(res)
}
const noteId = req.params.noteId
if (!noteId) {
setHistory(req.user.id, [], function (err, count) {
if (err) return errors.errorInternalError(res)
res.end()
})
} else {
getHistory(req.user.id, function (err, history) {
if (err) return errors.errorInternalError(res)
if (!history) return errors.errorNotFound(res)
delete history[noteId]
setHistory(req.user.id, history, function (err, count) {
if (err) return errors.errorInternalError(res)
res.end()
})
})
}
}
module.exports = History