Fix most glaring bugs and vulnerabilities

This commit is contained in:
Luka Romih
2023-05-08 23:25:25 +02:00
parent 9867875ef2
commit d0e53fee3b
144 changed files with 2231 additions and 2981 deletions
+65 -11
View File
@@ -11,6 +11,25 @@ exports.listComments = async (req, res) => {
filters.ctxId = null
}
if (filters.ctxType === 'entry_dict_int') {
const { dictionary_id: dictionaryId } = await Entry.fetch(filters.ctxId)
const isEditor = req.user.hasAnyDictionaryRole(dictionaryId)
const isPortalAdmin = req.user.hasRole('portal admin')
const isDictionariesAdmin = req.user.hasRole('dictionaries admin')
if (!(isEditor || isPortalAdmin || isDictionariesAdmin)) {
return res.status(400).end()
}
} else if (filters.ctxType === 'entry_consult_int') {
const isConsultantForEntry = req.user.isEditorOfConsultancyEntry(
filters.ctxId
)
const isPortalAdmin = req.user.hasRole('portal admin')
const isConsultancyAdmin = req.user.hasRole('consultancy admin')
if (!(isConsultantForEntry || isPortalAdmin || isConsultancyAdmin)) {
return res.status(400).end()
}
}
const {
pages_total: numberOfAllPages,
comments,
@@ -38,20 +57,55 @@ exports.createComment = async (req, res) => {
res.send({ comments, pagesTotal })
}
exports.seedComments = async (req, res) => {
const { commentCount } = req.params
await Comment.seed(commentCount)
res.send(`${commentCount} new comments generated`)
}
exports.clearComments = async (req, res) => {
await Comment.clear()
res.send('All comments cleared')
}
exports.updateStatus = async (req, res) => {
const commentId = req.body.params.id
const commentStatus = req.body.params.status
const { ctxType, ctxId } = await Comment.fetchContextById(commentId)
let canUpdateStatus = false
switch (ctxType) {
case 'portal':
if (req.user.hasRole('portal admin')) canUpdateStatus = true
break
case 'dictionary':
if (
req.user.hasRole('portal admin') ||
req.user.hasRole('dictionaries admin')
) {
canUpdateStatus = true
}
break
case 'consultancy':
if (
req.user.hasRole('portal admin') ||
req.user.hasRole('consultancy admin')
) {
canUpdateStatus = true
}
break
case 'entry_dict_ext': {
const { dictionary_id: dictionaryId } = await Entry.fetch(ctxId)
if (
req.user.hasRole('portal admin') ||
req.user.hasRole('dictionaries admin') ||
req.user.hasDictionaryRole(dictionaryId, 'administration')
) {
canUpdateStatus = true
}
break
}
default:
throw Error('Invalid context type')
}
if (!canUpdateStatus) return res.status(400).end()
await Comment.updateStatus(commentId, commentStatus)
res.send('Visibility changed')
}
+36 -37
View File
@@ -1,3 +1,4 @@
const user = require('../../../middleware/user')
const ConsultancyEntry = require('../../../models/consultancy-entry')
const Domain = require('../../../models/domain')
const User = require('../../../models/user')
@@ -16,20 +17,6 @@ const generateQuery = require('../../../models/helpers/search/generate-query')
const consultancy = {}
consultancy.listEntries = async (req, res) => {
const consultancyEntryList = await ConsultancyEntry.fetchAll()
const data = {}
data.consEntryList = consultancyEntryList
res.send(data)
}
consultancy.listNewEntries = async (req, res) => {
const consultancyNewEntryList = await ConsultancyEntry.fetchAllNew()
const data = {}
data.consultancyNewEntryList = consultancyNewEntryList
res.send(data)
}
consultancy.sendPaginationData = async (req, res) => {
let requestType = req.query.type
const isAdminPage = req.query.isAdmin === 'true'
@@ -325,9 +312,12 @@ consultancy.publish = async (req, res) => {
const author = await User.fetchUser(entry.authorId)
const portalName = await getInstanceSetting(`portal_name_${author.language}`)
const renderAsync = promisify(req.app.render.bind(req.app))
const emailHtml = await renderAsync('email/consultancy-publish-notify', {
portalName
})
const emailHtml = await renderAsync(
`email/consultancy-publish-notify_${author.language}`,
{
portalName
}
)
await email.send({
to: author.email,
subject: i18next.t('Objava terminološkega vprašanja', {
@@ -342,29 +332,38 @@ consultancy.publish = async (req, res) => {
res.send()
}
consultancy.updateQuestion = async (req, res) => {
const { id, questionTitle, domain: domainId, question, answer } = req.body
consultancy.updateQuestion = [
(req, res, next) => {
const { id } = req.body
if (!id) return res.status(400).send({})
if (!id) return res.status(400).send({})
if (questionTitle === '' || question === '' || answer === '') {
return res
.status(422)
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
req.entryId = id
next()
},
user.canConsultEntry,
async (req, res) => {
const { id, questionTitle, domain: domainId, question, answer } = req.body
if (questionTitle === '' || question === '' || answer === '') {
return res
.status(422)
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
}
const entry = await ConsultancyEntry.fetchById(id)
entry.domainPrimaryId = domainId > 0 ? domainId : null
entry.question = question
entry.answer = answer // helper.removeHtmlTags(answer).trim()
entry.title = questionTitle
// TODO Luka: Miha, update only fields that were updated.
await ConsultancyEntry.updateQuestion(entry)
await ConsultancyEntry.indexIntoSearchEngine(id, true)
res.send({})
}
const entry = await ConsultancyEntry.fetchById(id)
entry.domainPrimaryId = domainId > 0 ? domainId : null
entry.question = question
entry.answer = answer // helper.removeHtmlTags(answer).trim()
entry.title = questionTitle
// TODO Luka: Miha, update only fields that were updated.
await ConsultancyEntry.updateQuestion(entry)
await ConsultancyEntry.indexIntoSearchEngine(id, true)
res.send({})
}
]
consultancy.insertNonModerator = async (req, res) => {
const questionId = req.body.question_id
@@ -1,15 +0,0 @@
const DemoPaginacija = require('../../../models/demo-paginacija')
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
exports.list = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const page = +req.query.p > 0 ? +req.query.p : 1
const { pages_total: numberOfAllPages, results } = await DemoPaginacija.fetch(
resultsPerPage,
page
)
res.send({ page, numberOfAllPages, results })
}
+30 -19
View File
@@ -1,4 +1,5 @@
const { rm } = require('fs/promises')
const user = require('../../../middleware/user')
const Dictionary = require('../../../models/dictionary')
const Entry = require('../../../models/entry')
const Extraction = require('../../../models/extraction')
@@ -69,7 +70,11 @@ dictionary.deleteEntry = async (req, res) => {
await Promise.all([
Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
deleteEntryFromIndex(entryId, true),
minEntriesRequirementCheckAndAct.onDelete(dictionaryId, req.app)
minEntriesRequirementCheckAndAct.onDelete(
dictionaryId,
req.app,
req.determinedLanguage
)
])
res.end()
@@ -84,7 +89,11 @@ dictionary.deleteAllEntries = async (req, res) => {
await Promise.all([
Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
deleteDictionaryEntriesFromIndex(dictionaryId),
minEntriesRequirementCheckAndAct.onDelete(dictionaryId, req.app)
minEntriesRequirementCheckAndAct.onDelete(
dictionaryId,
req.app,
req.determinedLanguage
)
])
res.end()
@@ -124,12 +133,19 @@ dictionary.delete = async (req, res) => {
res.end()
}
dictionary.updateDomainLabels = async (req, res) => {
const { dictionaryId, payload } = req.body.params
dictionary.updateDomainLabels = [
(req, res, next) => {
req.dictionaryId = req.body.params.dictionaryId
next()
},
user.canAdministrateDictionary,
async (req, res) => {
const { dictionaryId, payload } = req.body.params
await Dictionary.updateDomainLabel(dictionaryId, payload)
res.end()
}
await Dictionary.updateDomainLabel(dictionaryId, payload)
res.end()
}
]
dictionary.renovateSecondaryDomains = async (req, res) => {
const data = req.body.params.payload
@@ -152,7 +168,11 @@ dictionary.listDictionaries = async (req, res) => {
const page = +req.query.p > 0 ? +req.query.p : 1
const { pages_total: numberOfAllPages, results } =
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, page)
await Dictionary.fetchAllAdminDictionaries(
req.determinedLanguage,
resultsPerPage,
page
)
res.send({ page, numberOfAllPages, results })
}
@@ -222,15 +242,6 @@ dictionary.listSecondaryDomainData = async (req, res) => {
})
}
dictionary.listSecondaryDomains = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const page = +req.query.p > 0 ? +req.query.p : 1
const { pages_total: numberOfAllPages, results } =
await Dictionary.fetchAllSecondaryDomains(resultsPerPage, page)
res.send({ page, numberOfAllPages, results })
}
dictionary.showImportFromFileForm = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const { dictionaryId } = req.params
@@ -252,7 +263,7 @@ dictionary.showExportToFileForm = async (req, res) => {
}
dictionary.importFromExtraction = async (req, res) => {
const { id: dictionaryId, extractionId } = req.params
const { dictionaryId, extractionId } = req.params
const { from, to } = req.body
const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
const toIndex = Number.isInteger(+(to === '' ? undefined : to))
@@ -279,7 +290,7 @@ dictionary.importFromExtraction = async (req, res) => {
}
dictionary.exportBegin = async (req, res) => {
const dictionaryId = req.params.id
const { dictionaryId } = req.params
const exportParams = {
isValidFilter:
req.body.isValidFilter === 'on' ? undefined : req.body.isValidFilter,
+2 -7
View File
@@ -166,7 +166,7 @@ extraction.ossSearch = [
extraction.ossConfirmParams = async (req, res) => {
const { id: extractionId } = req.params
const { ossParams } = await Extraction.fetch(extractionId)
const { ossParams } = req.extractionData
if (ossParams.status !== 'valid') throw Error('OSS params not valid')
await Extraction.updateOssParams(extractionId, {
params: ossParams.params,
@@ -177,7 +177,7 @@ extraction.ossConfirmParams = async (req, res) => {
extraction.begin = async (req, res) => {
const extractionId = req.params.id
const extraction = await Extraction.fetch(extractionId)
const extraction = req.extractionData
const canBegin = await checkIfcanBegin(extraction)
if (!canBegin) throw Error('Extraction does not qualify to be ran')
@@ -268,11 +268,6 @@ extraction.termCandidatesExport = async (req, res) => {
}
}
extraction.listFinishedForUser = async (req, res) => {
const extractions = await Extraction.fetchFinishedForUser(req.user.id)
res.send(extractions)
}
extraction.listTermCandidates = async (req, res) => {
const extractionId = req.params.id
const termCandidatesJson = await Extraction.fetchTermCandidatesJson(
+6 -2
View File
@@ -178,7 +178,8 @@ exports.listFilteredDictionaries = async (req, res) => {
hitsPerPage,
page,
orderAttribute,
orderIndex
orderIndex,
req.determinedLanguage
)
dictionaries = dictionaries.map(e => {
@@ -246,6 +247,9 @@ exports.showModalFilterResults = async (req, res) => {
const aggregationRaw = await searchEntryIndex(aggregateQuery)
const aggregation = await prepareAggregation(aggregationRaw)
const aggregation = await prepareAggregation(
aggregationRaw,
req.determinedLanguage
)
res.send(prepareSeachFilterData(aggregation, filters))
}
+4 -3
View File
@@ -1,9 +1,10 @@
const debug = require('debug')('termPortal:controllers/api/v1/system')
const Eurotermbank = require('../../../models/system/eurotermbank')
// const debug = require('debug')('termPortal:controllers/api/v1/system')
exports.handleCspReports = (req, res) => {
debug(req.body)
res.sendStatus(200)
// eslint-disable-next-line no-console
console.error(req.body)
res.end()
}
exports.syncWithEurotermbank = async (req, res) => {
+105 -6
View File
@@ -1,5 +1,14 @@
const { randomBytes } = require('crypto')
const { promisify } = require('util')
const RandomBytesAsync = promisify(randomBytes)
const User = require('../../../models/user')
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
const email = require('../../../models/email')
const { logout: logoutUser } = require('../../../middleware/user')
const { origin } = require('../../../config/keys')
const {
DEFAULT_HITS_PER_PAGE,
CHANGE_EMAIL_TOKEN_VALID_DAYS
} = require('../../../config/settings')
const users = {}
users.listUsers = async (req, res) => {
@@ -23,13 +32,103 @@ users.updateHitsPerPage = async (req, res) => {
res.status(200).send()
}
users.updateFristNameAndSurname = async (req, res) => {
const firstname = req.body.name
const surname = req.body.surname
users.updateBasicData = async (req, res) => {
const { firstName, lastName, email: newEmail } = req.body
await User.updateFirstNameAndLastName(req.user.userName, firstname, surname)
// TODO Validation (valid email format, ...).
res.status(200).send()
const oldEmail = await User.updateFirstNameAndLastName(
req.user.id,
firstName,
lastName
)
if (newEmail === oldEmail) return res.send()
if (await User.isEmailAlreadyTaken(newEmail)) {
req.flash('info', req.t('Elektronski naslov uporablja že drug uporabnik.'))
return res.send()
}
const changeEmailToken = (await RandomBytesAsync(32)).toString('hex')
await User.saveChangeEmailToken(req.user.id, changeEmailToken, newEmail)
let changeEmailLink = new URL('/sprememba-elektronskega-naslova', origin)
changeEmailLink.searchParams.set('token', changeEmailToken)
changeEmailLink = changeEmailLink.href
const renderAsync = promisify(req.app.render.bind(req.app))
const emailHtml = await renderAsync(
`email/user-change-email-token_${req.language}`,
{
username: req.user.userName,
changeEmailLink
}
)
await email.send({
to: newEmail,
subject: req.t('Sprememba elektronskega naslova'),
html: emailHtml
})
const message =
req.t(
'Na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili menjavo elektronskega naslova. Povezava za potrditev je veljavna '
) +
`${CHANGE_EMAIL_TOKEN_VALID_DAYS} ` +
req.t('dni.')
req.flash('info', message)
res.send()
}
users.updatePassword = async (req, res) => {
const { passwordOld, passwordNew, passwordNewRepeat } = req.body
// TODO Validation (mirror front end validation, ...).
if (passwordNew !== passwordNewRepeat) {
const err = Error(req.t('Gesli se ne ujemata'))
err.status = 403
err.displayInProd = true
throw err
}
await User.changePassword(req.user.id, passwordOld, passwordNew, req.t)
// TODO Invalidate or log out all session for this user. More details in deleteCurrent method TODO.
const renderAsync = promisify(req.app.render.bind(req.app))
const emailHtml = await renderAsync(
`email/user-change-password_${req.language}`,
{
username: req.user.userName
}
)
await email.send({
to: req.user.email,
subject: req.t('Sprememba gesla'),
html: emailHtml
})
req.flash('info', 'Geslo je bilo spremenjeno.')
res.send()
}
users.deleteCurrent = [
async (req, res, next) => {
await User.closeAccount(req.user.id)
next()
},
logoutUser,
(req, res) => {
// TODO Invalidate or log out all session for this user. Current workaround is in passport.deserializeUser.
// You can probably do it in 1 of 3 ways:
// 1. Brute force; loop through all sessions (using session store's all or ids methods),
// look up their values and remote the ones with user's id
// 2. Include user Id as part of session key; something similar to https://github.com/tj/connect-redis/issues/210#issuecomment-1336545115
// 3. Create some kind of inverse index, mapping user id to his/hers sessions (also mentioned in issue linked above)
req.flash('info', req.t('Vaš uporabniški račun je bil uspešno izbrisan.'))
res.send()
}
]
module.exports = users