feat: page Rules access check
This commit is contained in:
@@ -131,6 +131,7 @@ router.get('/*', async (req, res, next) => {
|
||||
if (pageArgs.path === 'home') {
|
||||
return res.redirect('/login')
|
||||
} else {
|
||||
_.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
||||
return res.render('unauthorized', { action: 'view'})
|
||||
}
|
||||
}
|
||||
@@ -151,7 +152,11 @@ router.get('/*', async (req, res, next) => {
|
||||
res.render('welcome')
|
||||
} else {
|
||||
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
|
||||
res.status(404).render('new', { pagePath: req.path })
|
||||
if (WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
|
||||
res.status(404).render('new', { pagePath: req.path })
|
||||
} else {
|
||||
res.render('notfound', { action: 'view'})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@@ -15,6 +15,7 @@ module.exports = {
|
||||
guest: {
|
||||
cacheExpiration: moment.utc().subtract(1, 'd')
|
||||
},
|
||||
groups: {},
|
||||
|
||||
/**
|
||||
* Initialize the authentication module
|
||||
@@ -22,23 +23,27 @@ module.exports = {
|
||||
init() {
|
||||
this.passport = passport
|
||||
|
||||
passport.serializeUser(function (user, done) {
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(function (id, done) {
|
||||
WIKI.models.users.query().findById(id).then((user) => {
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await WIKI.models.users.query().findById(id).modifyEager('groups', builder => {
|
||||
builder.select('groups.id', 'permissions')
|
||||
})
|
||||
if (user) {
|
||||
done(null, user)
|
||||
} else {
|
||||
done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
|
||||
}
|
||||
return true
|
||||
}).catch((err) => {
|
||||
} catch (err) {
|
||||
done(err, null)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.reloadGroups()
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -117,13 +122,14 @@ module.exports = {
|
||||
res.cookie('jwt', newToken.token, { expires: moment().add(365, 'days').toDate() })
|
||||
}
|
||||
} catch (err) {
|
||||
WIKI.logger.warn(err)
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
// JWT is NOT valid, set as guest
|
||||
if (!user) {
|
||||
if (WIKI.auth.guest.cacheExpiration ) {
|
||||
if (true || WIKI.auth.guest.cacheExpiration.isSameOrBefore(moment.utc())) {
|
||||
WIKI.auth.guest = await WIKI.models.users.getGuestUser()
|
||||
WIKI.auth.guest.cacheExpiration = moment.utc().add(1, 'm')
|
||||
}
|
||||
@@ -146,18 +152,99 @@ module.exports = {
|
||||
* @param {Array<String>} permissions
|
||||
* @param {String|Boolean} path
|
||||
*/
|
||||
checkAccess(user, permissions = [], path = false) {
|
||||
checkAccess(user, permissions = [], page = false) {
|
||||
// System Admin
|
||||
if (_.includes(user.permissions, 'manage:system')) {
|
||||
return true
|
||||
}
|
||||
|
||||
const userPermissions = user.permissions ? user.permissions : user.getGlobalPermissions()
|
||||
|
||||
// Check Global Permissions
|
||||
if (_.intersection(user.permissions, permissions).length < 1) {
|
||||
if (_.intersection(userPermissions, permissions).length < 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
console.info('---------------------')
|
||||
|
||||
// Check Page Rules
|
||||
if (path && user.groups) {
|
||||
let checkState = {
|
||||
deny: false,
|
||||
match: false,
|
||||
specificity: ''
|
||||
}
|
||||
user.groups.forEach(grp => {
|
||||
const grpId = _.isObject(grp) ? _.get(grp, 'id', 0) : grp
|
||||
_.get(WIKI.auth.groups, `${grpId}.pageRules`, []).forEach(rule => {
|
||||
console.info(page.path)
|
||||
console.info(rule)
|
||||
switch(rule.match) {
|
||||
case 'START':
|
||||
if (_.startsWith(`/${page.path}`, `/${rule.path}`)) {
|
||||
checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['END', 'REGEX', 'EXACT'] })
|
||||
}
|
||||
break
|
||||
case 'END':
|
||||
if (_.endsWith(page.path, rule.path)) {
|
||||
checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['REGEX', 'EXACT'] })
|
||||
}
|
||||
break
|
||||
case 'REGEX':
|
||||
const reg = new RegExp(rule.path)
|
||||
if (reg.test(page.path)) {
|
||||
checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['EXACT'] })
|
||||
}
|
||||
case 'EXACT':
|
||||
if (`/${page.path}` === `/${rule.path}`) {
|
||||
checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: [] })
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
console.info('DAKSJDHKASJD')
|
||||
console.info(checkState)
|
||||
|
||||
return (checkState.match && !checkState.deny)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* Check and apply Page Rule specificity
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
_applyPageRuleSpecificity ({ rule, checkState, higherPriority = [] }) {
|
||||
if (rule.path.length === checkState.specificity.length) {
|
||||
// Do not override higher priority rules
|
||||
if (_.includes(higherPriority, checkState.match)) {
|
||||
return checkState
|
||||
}
|
||||
// Do not override a previous DENY rule with same match
|
||||
if (rule.match === checkState.match && checkState.deny && !rule.deny) {
|
||||
return checkState
|
||||
}
|
||||
} else if (rule.path.length < checkState.specificity.length) {
|
||||
// Do not override higher specificity rules
|
||||
return checkState
|
||||
}
|
||||
|
||||
return {
|
||||
deny: rule.deny,
|
||||
match: rule.match,
|
||||
specificity: rule.path
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reload Groups from DB
|
||||
*/
|
||||
async reloadGroups() {
|
||||
const groupsArray = await WIKI.models.groups.query()
|
||||
this.groups = _.keyBy(groupsArray, 'id')
|
||||
}
|
||||
}
|
||||
|
@@ -2,41 +2,41 @@
|
||||
/* global WIKI */
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
comments(obj, args, context, info) {
|
||||
return WIKI.models.Comment.findAll({ where: args })
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
createComment(obj, args) {
|
||||
return WIKI.models.Comment.create({
|
||||
content: args.content,
|
||||
author: args.userId,
|
||||
document: args.documentId
|
||||
})
|
||||
},
|
||||
deleteComment(obj, args) {
|
||||
return WIKI.models.Comment.destroy({
|
||||
where: {
|
||||
id: args.id
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
},
|
||||
modifyComment(obj, args) {
|
||||
return WIKI.models.Comment.update({
|
||||
content: args.content
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
}
|
||||
},
|
||||
Comment: {
|
||||
author(cm) {
|
||||
return cm.getAuthor()
|
||||
},
|
||||
document(cm) {
|
||||
return cm.getDocument()
|
||||
}
|
||||
}
|
||||
// Query: {
|
||||
// comments(obj, args, context, info) {
|
||||
// return WIKI.models.Comment.findAll({ where: args })
|
||||
// }
|
||||
// },
|
||||
// Mutation: {
|
||||
// createComment(obj, args) {
|
||||
// return WIKI.models.Comment.create({
|
||||
// content: args.content,
|
||||
// author: args.userId,
|
||||
// document: args.documentId
|
||||
// })
|
||||
// },
|
||||
// deleteComment(obj, args) {
|
||||
// return WIKI.models.Comment.destroy({
|
||||
// where: {
|
||||
// id: args.id
|
||||
// },
|
||||
// limit: 1
|
||||
// })
|
||||
// },
|
||||
// modifyComment(obj, args) {
|
||||
// return WIKI.models.Comment.update({
|
||||
// content: args.content
|
||||
// }, {
|
||||
// where: { id: args.id }
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// Comment: {
|
||||
// author(cm) {
|
||||
// return cm.getAuthor()
|
||||
// },
|
||||
// document(cm) {
|
||||
// return cm.getDocument()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@@ -1,46 +0,0 @@
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
documents(obj, args, context, info) {
|
||||
return WIKI.models.Document.findAll({ where: args })
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
createDocument(obj, args) {
|
||||
return WIKI.models.Document.create(args)
|
||||
},
|
||||
deleteDocument(obj, args) {
|
||||
return WIKI.models.Document.destroy({
|
||||
where: {
|
||||
id: args.id
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
},
|
||||
modifyDocument(obj, args) {
|
||||
return WIKI.models.Document.update({
|
||||
title: args.title,
|
||||
subtitle: args.subtitle
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
},
|
||||
moveDocument(obj, args) {
|
||||
return WIKI.models.Document.update({
|
||||
path: args.path
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
}
|
||||
},
|
||||
Document: {
|
||||
comments(doc) {
|
||||
return doc.getComments()
|
||||
},
|
||||
tags(doc) {
|
||||
return doc.getTags()
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,48 +4,48 @@
|
||||
const gql = require('graphql')
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
files(obj, args, context, info) {
|
||||
return WIKI.models.File.findAll({ where: args })
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
uploadFile(obj, args) {
|
||||
// todo
|
||||
return WIKI.models.File.create(args)
|
||||
},
|
||||
deleteFile(obj, args) {
|
||||
return WIKI.models.File.destroy({
|
||||
where: {
|
||||
id: args.id
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
},
|
||||
renameFile(obj, args) {
|
||||
return WIKI.models.File.update({
|
||||
filename: args.filename
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
},
|
||||
moveFile(obj, args) {
|
||||
return WIKI.models.File.findById(args.fileId).then(fl => {
|
||||
if (!fl) {
|
||||
throw new gql.GraphQLError('Invalid File ID')
|
||||
}
|
||||
return WIKI.models.Folder.findById(args.folderId).then(fld => {
|
||||
if (!fld) {
|
||||
throw new gql.GraphQLError('Invalid Folder ID')
|
||||
}
|
||||
return fl.setFolder(fld)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
File: {
|
||||
folder(fl) {
|
||||
return fl.getFolder()
|
||||
}
|
||||
}
|
||||
// Query: {
|
||||
// files(obj, args, context, info) {
|
||||
// return WIKI.models.File.findAll({ where: args })
|
||||
// }
|
||||
// },
|
||||
// Mutation: {
|
||||
// uploadFile(obj, args) {
|
||||
// // todo
|
||||
// return WIKI.models.File.create(args)
|
||||
// },
|
||||
// deleteFile(obj, args) {
|
||||
// return WIKI.models.File.destroy({
|
||||
// where: {
|
||||
// id: args.id
|
||||
// },
|
||||
// limit: 1
|
||||
// })
|
||||
// },
|
||||
// renameFile(obj, args) {
|
||||
// return WIKI.models.File.update({
|
||||
// filename: args.filename
|
||||
// }, {
|
||||
// where: { id: args.id }
|
||||
// })
|
||||
// },
|
||||
// moveFile(obj, args) {
|
||||
// return WIKI.models.File.findById(args.fileId).then(fl => {
|
||||
// if (!fl) {
|
||||
// throw new gql.GraphQLError('Invalid File ID')
|
||||
// }
|
||||
// return WIKI.models.Folder.findById(args.folderId).then(fld => {
|
||||
// if (!fld) {
|
||||
// throw new gql.GraphQLError('Invalid Folder ID')
|
||||
// }
|
||||
// return fl.setFolder(fld)
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// File: {
|
||||
// folder(fl) {
|
||||
// return fl.getFolder()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@@ -2,34 +2,34 @@
|
||||
/* global WIKI */
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
folders(obj, args, context, info) {
|
||||
return WIKI.models.Folder.findAll({ where: args })
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
createFolder(obj, args) {
|
||||
return WIKI.models.Folder.create(args)
|
||||
},
|
||||
deleteFolder(obj, args) {
|
||||
return WIKI.models.Folder.destroy({
|
||||
where: {
|
||||
id: args.id
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
},
|
||||
renameFolder(obj, args) {
|
||||
return WIKI.models.Folder.update({
|
||||
name: args.name
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
}
|
||||
},
|
||||
Folder: {
|
||||
files(grp) {
|
||||
return grp.getFiles()
|
||||
}
|
||||
}
|
||||
// Query: {
|
||||
// folders(obj, args, context, info) {
|
||||
// return WIKI.models.Folder.findAll({ where: args })
|
||||
// }
|
||||
// },
|
||||
// Mutation: {
|
||||
// createFolder(obj, args) {
|
||||
// return WIKI.models.Folder.create(args)
|
||||
// },
|
||||
// deleteFolder(obj, args) {
|
||||
// return WIKI.models.Folder.destroy({
|
||||
// where: {
|
||||
// id: args.id
|
||||
// },
|
||||
// limit: 1
|
||||
// })
|
||||
// },
|
||||
// renameFolder(obj, args) {
|
||||
// return WIKI.models.Folder.update({
|
||||
// name: args.name
|
||||
// }, {
|
||||
// where: { id: args.id }
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// Folder: {
|
||||
// files(grp) {
|
||||
// return grp.getFiles()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
const graphHelper = require('../../helpers/graph')
|
||||
const safeRegex = require('safe-regex')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
@@ -44,6 +45,7 @@ module.exports = {
|
||||
pageRules: JSON.stringify([]),
|
||||
isSystem: false
|
||||
})
|
||||
await WIKI.auth.reloadGroups()
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('Group created successfully.'),
|
||||
group
|
||||
@@ -51,6 +53,7 @@ module.exports = {
|
||||
},
|
||||
async delete(obj, args) {
|
||||
await WIKI.models.groups.query().deleteById(args.id)
|
||||
await WIKI.auth.reloadGroups()
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('Group has been deleted.')
|
||||
}
|
||||
@@ -70,11 +73,20 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
async update(obj, args) {
|
||||
if(_.some(args.pageRules, pr => {
|
||||
return pr.match !== 'REGEX' || safeRegex(pr.path)
|
||||
})) {
|
||||
throw new gql.GraphQLError('Some Page Rules contains unsafe or exponential time regex.')
|
||||
}
|
||||
|
||||
await WIKI.models.groups.query().patch({
|
||||
name: args.name,
|
||||
permissions: JSON.stringify(args.permissions),
|
||||
pageRules: JSON.stringify(args.pageRules)
|
||||
}).where('id', args.id)
|
||||
|
||||
await WIKI.auth.reloadGroups()
|
||||
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('Group has been updated.')
|
||||
}
|
||||
|
@@ -31,6 +31,9 @@ module.exports = {
|
||||
namespacing: WIKI.config.lang.namespacing,
|
||||
namespaces: WIKI.config.lang.namespaces
|
||||
}
|
||||
},
|
||||
translations (obj, args, context, info) {
|
||||
return WIKI.lang.getByNamespace(args.locale, args.namespace)
|
||||
}
|
||||
},
|
||||
LocalizationMutation: {
|
||||
|
@@ -16,15 +16,6 @@ module.exports = {
|
||||
offsetPage: args.offsetPage || 0,
|
||||
offsetSize: args.offsetSize || 100
|
||||
})
|
||||
},
|
||||
async list(obj, args, context, info) {
|
||||
return WIKI.models.pages.query().select(
|
||||
'pages.*',
|
||||
WIKI.models.pages.relatedQuery('users').count().as('userCount')
|
||||
)
|
||||
},
|
||||
async single(obj, args, context, info) {
|
||||
return WIKI.models.pages.query().findById(args.id)
|
||||
}
|
||||
},
|
||||
PageMutation: {
|
||||
|
@@ -1,53 +0,0 @@
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
const gql = require('graphql')
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
rights(obj, args, context, info) {
|
||||
return WIKI.models.Right.findAll({ where: args })
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
addRightToGroup(obj, args) {
|
||||
return WIKI.models.Group.findById(args.groupId).then(grp => {
|
||||
if (!grp) {
|
||||
throw new gql.GraphQLError('Invalid Group ID')
|
||||
}
|
||||
return WIKI.models.Right.create({
|
||||
path: args.path,
|
||||
role: args.role,
|
||||
exact: args.exact,
|
||||
allow: args.allow,
|
||||
group: grp
|
||||
})
|
||||
})
|
||||
},
|
||||
removeRightFromGroup(obj, args) {
|
||||
return WIKI.models.Right.destroy({
|
||||
where: {
|
||||
id: args.rightId
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
},
|
||||
modifyRight(obj, args) {
|
||||
return WIKI.models.Right.update({
|
||||
path: args.path,
|
||||
role: args.role,
|
||||
exact: args.exact,
|
||||
allow: args.allow
|
||||
}, {
|
||||
where: {
|
||||
id: args.id
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
Right: {
|
||||
group(rt) {
|
||||
return rt.getGroup()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
settings(obj, args, context, info) {
|
||||
return WIKI.models.Setting.findAll({ where: args, raw: true }).then(entries => {
|
||||
return _.map(entries, entry => {
|
||||
entry.config = JSON.stringify(entry.config)
|
||||
return entry
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
setConfigEntry(obj, args) {
|
||||
return WIKI.models.Setting.update({
|
||||
value: args.value
|
||||
}, { where: { key: args.key } })
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,13 +20,9 @@ module.exports = {
|
||||
Query: {
|
||||
async system() { return {} }
|
||||
},
|
||||
Mutation: {
|
||||
async system() { return {} }
|
||||
},
|
||||
SystemQuery: {
|
||||
async info() { return {} }
|
||||
},
|
||||
SystemMutation: { },
|
||||
SystemInfo: {
|
||||
configFile() {
|
||||
return path.join(process.cwd(), 'config.yml')
|
||||
|
@@ -4,60 +4,60 @@
|
||||
const gql = require('graphql')
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
tags(obj, args, context, info) {
|
||||
return WIKI.models.Tag.findAll({ where: args })
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
assignTagToDocument(obj, args) {
|
||||
return WIKI.models.Tag.findById(args.tagId).then(tag => {
|
||||
if (!tag) {
|
||||
throw new gql.GraphQLError('Invalid Tag ID')
|
||||
}
|
||||
return WIKI.models.Document.findById(args.documentId).then(doc => {
|
||||
if (!doc) {
|
||||
throw new gql.GraphQLError('Invalid Document ID')
|
||||
}
|
||||
return tag.addDocument(doc)
|
||||
})
|
||||
})
|
||||
},
|
||||
createTag(obj, args) {
|
||||
return WIKI.models.Tag.create(args)
|
||||
},
|
||||
deleteTag(obj, args) {
|
||||
return WIKI.models.Tag.destroy({
|
||||
where: {
|
||||
id: args.id
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
},
|
||||
removeTagFromDocument(obj, args) {
|
||||
return WIKI.models.Tag.findById(args.tagId).then(tag => {
|
||||
if (!tag) {
|
||||
throw new gql.GraphQLError('Invalid Tag ID')
|
||||
}
|
||||
return WIKI.models.Document.findById(args.documentId).then(doc => {
|
||||
if (!doc) {
|
||||
throw new gql.GraphQLError('Invalid Document ID')
|
||||
}
|
||||
return tag.removeDocument(doc)
|
||||
})
|
||||
})
|
||||
},
|
||||
renameTag(obj, args) {
|
||||
return WIKI.models.Group.update({
|
||||
key: args.key
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
}
|
||||
},
|
||||
Tag: {
|
||||
documents(tag) {
|
||||
return tag.getDocuments()
|
||||
}
|
||||
}
|
||||
// Query: {
|
||||
// tags(obj, args, context, info) {
|
||||
// return WIKI.models.Tag.findAll({ where: args })
|
||||
// }
|
||||
// },
|
||||
// Mutation: {
|
||||
// assignTagToDocument(obj, args) {
|
||||
// return WIKI.models.Tag.findById(args.tagId).then(tag => {
|
||||
// if (!tag) {
|
||||
// throw new gql.GraphQLError('Invalid Tag ID')
|
||||
// }
|
||||
// return WIKI.models.Document.findById(args.documentId).then(doc => {
|
||||
// if (!doc) {
|
||||
// throw new gql.GraphQLError('Invalid Document ID')
|
||||
// }
|
||||
// return tag.addDocument(doc)
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
// createTag(obj, args) {
|
||||
// return WIKI.models.Tag.create(args)
|
||||
// },
|
||||
// deleteTag(obj, args) {
|
||||
// return WIKI.models.Tag.destroy({
|
||||
// where: {
|
||||
// id: args.id
|
||||
// },
|
||||
// limit: 1
|
||||
// })
|
||||
// },
|
||||
// removeTagFromDocument(obj, args) {
|
||||
// return WIKI.models.Tag.findById(args.tagId).then(tag => {
|
||||
// if (!tag) {
|
||||
// throw new gql.GraphQLError('Invalid Tag ID')
|
||||
// }
|
||||
// return WIKI.models.Document.findById(args.documentId).then(doc => {
|
||||
// if (!doc) {
|
||||
// throw new gql.GraphQLError('Invalid Document ID')
|
||||
// }
|
||||
// return tag.removeDocument(doc)
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
// renameTag(obj, args) {
|
||||
// return WIKI.models.Group.update({
|
||||
// key: args.key
|
||||
// }, {
|
||||
// where: { id: args.id }
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// Tag: {
|
||||
// documents(tag) {
|
||||
// return tag.getDocuments()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@@ -1,12 +0,0 @@
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
translations (obj, args, context, info) {
|
||||
return WIKI.lang.getByNamespace(args.locale, args.namespace)
|
||||
}
|
||||
},
|
||||
Mutation: {},
|
||||
Translation: {}
|
||||
}
|
@@ -22,7 +22,6 @@ module.exports = {
|
||||
},
|
||||
async single(obj, args, context, info) {
|
||||
let usr = await WIKI.models.users.query().findById(args.id)
|
||||
console.info(usr)
|
||||
usr.password = ''
|
||||
usr.tfaSecret = ''
|
||||
return usr
|
||||
|
@@ -1,37 +1,32 @@
|
||||
|
||||
|
||||
# ENUMS
|
||||
|
||||
enum FileType {
|
||||
binary
|
||||
image
|
||||
}
|
||||
|
||||
enum RightRole {
|
||||
read
|
||||
write
|
||||
manage
|
||||
}
|
||||
# ====================== #
|
||||
# Wiki.js GraphQL Schema #
|
||||
# ====================== #
|
||||
|
||||
# DIRECTIVES
|
||||
# ----------
|
||||
|
||||
directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFINITION
|
||||
|
||||
# TYPES
|
||||
# -----
|
||||
|
||||
# Generic Key Value Pair
|
||||
type KeyValuePair {
|
||||
key: String!
|
||||
value: String!
|
||||
}
|
||||
# General Key Value Pair Input
|
||||
input KeyValuePairInput {
|
||||
key: String!
|
||||
value: String!
|
||||
}
|
||||
|
||||
# Generic Mutation Response
|
||||
type DefaultResponse {
|
||||
responseResult: ResponseStatus
|
||||
}
|
||||
|
||||
# Mutation Status
|
||||
type ResponseStatus {
|
||||
succeeded: Boolean!
|
||||
errorCode: Int!
|
||||
@@ -39,220 +34,14 @@ type ResponseStatus {
|
||||
message: String
|
||||
}
|
||||
|
||||
type Comment {
|
||||
id: Int!
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
content: String
|
||||
document: Document!
|
||||
author: User!
|
||||
}
|
||||
|
||||
type Document {
|
||||
id: Int!
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
path: String!
|
||||
title: String!
|
||||
subtitle: String
|
||||
parentPath: String
|
||||
parentTitle: String
|
||||
isDirectory: Boolean!
|
||||
isEntry: Boolean!
|
||||
searchContent: String
|
||||
comments: [Comment]
|
||||
tags: [Tag]
|
||||
}
|
||||
|
||||
type File {
|
||||
id: Int!
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
category: FileType!
|
||||
mime: String!
|
||||
extra: String
|
||||
filename: String!
|
||||
basename: String!
|
||||
filesize: Int!
|
||||
folder: Folder
|
||||
}
|
||||
|
||||
type Folder {
|
||||
id: Int!
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
name: String!
|
||||
files: [File]
|
||||
}
|
||||
|
||||
type Right {
|
||||
id: Int!
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
path: String!
|
||||
role: RightRole!
|
||||
exact: Boolean!
|
||||
allow: Boolean!
|
||||
group: Group!
|
||||
}
|
||||
|
||||
type Setting {
|
||||
id: Int!
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
key: String!
|
||||
config: String!
|
||||
}
|
||||
|
||||
# Tags are attached to one or more documents
|
||||
type Tag {
|
||||
id: Int!
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
key: String!
|
||||
documents: [Document]
|
||||
}
|
||||
|
||||
type Translation {
|
||||
key: String!
|
||||
value: String!
|
||||
}
|
||||
|
||||
type OperationResult {
|
||||
succeeded: Boolean!
|
||||
message: String
|
||||
data: String
|
||||
}
|
||||
# ROOT
|
||||
# ----
|
||||
|
||||
# Query (Read)
|
||||
type Query {
|
||||
comments(id: Int): [Comment]
|
||||
documents(id: Int, path: String): [Document]
|
||||
files(id: Int): [File]
|
||||
folders(id: Int, name: String): [Folder]
|
||||
rights(id: Int): [Right]
|
||||
settings(key: String): [Setting]
|
||||
tags(key: String): [Tag]
|
||||
translations(locale: String!, namespace: String!): [Translation]
|
||||
}
|
||||
type Query
|
||||
|
||||
# Mutations (Create, Update, Delete)
|
||||
type Mutation {
|
||||
addRightToGroup(
|
||||
groupId: Int!
|
||||
path: String!
|
||||
role: RightRole!
|
||||
exact: Boolean!
|
||||
allow: Boolean!
|
||||
): Right
|
||||
|
||||
assignTagToDocument(
|
||||
tagId: Int!
|
||||
documentId: Int!
|
||||
): OperationResult
|
||||
|
||||
createComment(
|
||||
userId: Int!
|
||||
documentId: Int!
|
||||
content: String!
|
||||
): Comment
|
||||
|
||||
createDocument(
|
||||
path: String!
|
||||
title: String!
|
||||
subtitle: String
|
||||
): Document
|
||||
|
||||
createFolder(
|
||||
name: String!
|
||||
): Folder
|
||||
|
||||
createTag(
|
||||
name: String!
|
||||
): Tag
|
||||
|
||||
deleteComment(
|
||||
id: Int!
|
||||
): OperationResult
|
||||
|
||||
deleteDocument(
|
||||
id: Int!
|
||||
): OperationResult
|
||||
|
||||
deleteFile(
|
||||
id: Int!
|
||||
): OperationResult
|
||||
|
||||
deleteFolder(
|
||||
id: Int!
|
||||
): OperationResult
|
||||
|
||||
deleteTag(
|
||||
id: Int!
|
||||
): OperationResult
|
||||
|
||||
modifyComment(
|
||||
id: Int!
|
||||
content: String!
|
||||
): Document
|
||||
|
||||
modifyDocument(
|
||||
id: Int!
|
||||
title: String
|
||||
subtitle: String
|
||||
): Document
|
||||
|
||||
modifyRight(
|
||||
id: Int!
|
||||
path: String
|
||||
role: RightRole
|
||||
exact: Boolean
|
||||
allow: Boolean
|
||||
): Right
|
||||
|
||||
moveDocument(
|
||||
id: Int!
|
||||
path: String!
|
||||
): OperationResult
|
||||
|
||||
moveFile(
|
||||
id: Int!
|
||||
folderId: Int!
|
||||
): OperationResult
|
||||
|
||||
renameFile(
|
||||
id: Int!
|
||||
name: String!
|
||||
): OperationResult
|
||||
|
||||
renameFolder(
|
||||
id: Int!
|
||||
name: String!
|
||||
): OperationResult
|
||||
|
||||
renameTag(
|
||||
id: Int!
|
||||
key: String!
|
||||
): OperationResult
|
||||
|
||||
removeTagFromDocument(
|
||||
tagId: Int!
|
||||
documentId: Int!
|
||||
): OperationResult
|
||||
|
||||
removeRightFromGroup(
|
||||
rightId: Int!
|
||||
): OperationResult
|
||||
|
||||
setConfigEntry(
|
||||
key: String!
|
||||
value: String!
|
||||
): OperationResult
|
||||
|
||||
uploadFile(
|
||||
category: FileType!
|
||||
filename: String!
|
||||
): File
|
||||
}
|
||||
type Mutation
|
||||
|
||||
# Subscriptions (Push, Real-time)
|
||||
type Subscription
|
||||
|
@@ -89,7 +89,7 @@ type PageRule {
|
||||
id: String!
|
||||
deny: Boolean!
|
||||
match: PageRuleMatch!
|
||||
roles: [PageRuleRole]!
|
||||
roles: [String]!
|
||||
path: String!
|
||||
locales: [String]!
|
||||
}
|
||||
@@ -98,24 +98,11 @@ input PageRuleInput {
|
||||
id: String!
|
||||
deny: Boolean!
|
||||
match: PageRuleMatch!
|
||||
roles: [PageRuleRole]!
|
||||
roles: [String]!
|
||||
path: String!
|
||||
locales: [String]!
|
||||
}
|
||||
|
||||
enum PageRuleRole {
|
||||
READ
|
||||
WRITE
|
||||
MANAGE
|
||||
DELETE
|
||||
AS_READ
|
||||
AS_WRITE
|
||||
AS_MANAGE
|
||||
CM_READ
|
||||
CM_WRITE
|
||||
CM_MANAGE
|
||||
}
|
||||
|
||||
enum PageRuleMatch {
|
||||
START
|
||||
EXACT
|
||||
|
@@ -17,6 +17,7 @@ extend type Mutation {
|
||||
type LocalizationQuery {
|
||||
locales: [LocalizationLocale]
|
||||
config: LocalizationConfig
|
||||
translations(locale: String!, namespace: String!): [Translation]
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
@@ -57,3 +58,8 @@ type LocalizationConfig {
|
||||
namespacing: Boolean!
|
||||
namespaces: [String]!
|
||||
}
|
||||
|
||||
type Translation {
|
||||
key: String!
|
||||
value: String!
|
||||
}
|
||||
|
@@ -19,19 +19,7 @@ type PageQuery {
|
||||
id: Int!
|
||||
offsetPage: Int
|
||||
offsetSize: Int
|
||||
): PageHistoryResult
|
||||
|
||||
list(
|
||||
filter: String
|
||||
orderBy: String
|
||||
): [PageMinimal]
|
||||
|
||||
single(
|
||||
id: Int
|
||||
path: String
|
||||
locale: String
|
||||
isPrivate: Boolean
|
||||
): Page
|
||||
): PageHistoryResult @auth(requires: ["manage:system", "read:pages"])
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
@@ -82,21 +70,8 @@ type PageResponse {
|
||||
page: Page
|
||||
}
|
||||
|
||||
type PageMinimal {
|
||||
id: Int!
|
||||
name: String!
|
||||
userCount: Int
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
}
|
||||
|
||||
type Page {
|
||||
id: Int!
|
||||
name: String!
|
||||
rights: [Right]
|
||||
users: [User]
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
}
|
||||
|
||||
type PageHistory {
|
||||
|
@@ -49,7 +49,7 @@ type SiteConfig {
|
||||
description: String!
|
||||
robots: [String]!
|
||||
analyticsService: String!
|
||||
analyticsId: String!
|
||||
analyticsId: String!
|
||||
company: String!
|
||||
hasLogo: Boolean!
|
||||
logoIsSquare: Boolean!
|
||||
|
@@ -6,50 +6,42 @@ extend type Query {
|
||||
system: SystemQuery
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
system: SystemMutation
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
# QUERIES
|
||||
# -----------------------------------------------
|
||||
|
||||
type SystemQuery {
|
||||
info: SystemInfo @auth(requires: ["manage:system"])
|
||||
info: SystemInfo
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
# MUTATIONS
|
||||
# -----------------------------------------------
|
||||
|
||||
type SystemMutation {
|
||||
todo: String
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
# TYPES
|
||||
# -----------------------------------------------
|
||||
|
||||
type SystemInfo {
|
||||
configFile: String
|
||||
cpuCores: Int
|
||||
currentVersion: String
|
||||
dbHost: String
|
||||
dbType: String
|
||||
dbVersion: String
|
||||
groupsTotal: Int
|
||||
hostname: String
|
||||
latestVersion: String
|
||||
latestVersionReleaseDate: Date
|
||||
nodeVersion: String
|
||||
operatingSystem: String
|
||||
pagesTotal: Int
|
||||
platform: String
|
||||
ramTotal: String
|
||||
redisHost: String
|
||||
redisTotalRAM: String
|
||||
redisUsedRAM: String
|
||||
redisVersion: String
|
||||
usersTotal: Int
|
||||
workingDirectory: String
|
||||
configFile: String @auth(requires: ["manage:system"])
|
||||
cpuCores: Int @auth(requires: ["manage:system"])
|
||||
currentVersion: String @auth(requires: ["manage:system"])
|
||||
dbHost: String @auth(requires: ["manage:system"])
|
||||
dbType: String @auth(requires: ["manage:system"])
|
||||
dbVersion: String @auth(requires: ["manage:system"])
|
||||
groupsTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:groups", "write:groups", "manage:users", "write:users"])
|
||||
hostname: String @auth(requires: ["manage:system"])
|
||||
latestVersion: String @auth(requires: ["manage:system"])
|
||||
latestVersionReleaseDate: Date @auth(requires: ["manage:system"])
|
||||
nodeVersion: String @auth(requires: ["manage:system"])
|
||||
operatingSystem: String @auth(requires: ["manage:system"])
|
||||
pagesTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:pages", "delete:pages"])
|
||||
platform: String @auth(requires: ["manage:system"])
|
||||
ramTotal: String @auth(requires: ["manage:system"])
|
||||
redisHost: String @auth(requires: ["manage:system"])
|
||||
redisTotalRAM: String @auth(requires: ["manage:system"])
|
||||
redisUsedRAM: String @auth(requires: ["manage:system"])
|
||||
redisVersion: String @auth(requires: ["manage:system"])
|
||||
usersTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:groups", "write:groups", "manage:users", "write:users"])
|
||||
workingDirectory: String @auth(requires: ["manage:system"])
|
||||
}
|
||||
|
@@ -101,6 +101,10 @@ module.exports = class User extends Model {
|
||||
await this.generateHash()
|
||||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
// Instance Methods
|
||||
// ------------------------------------------------
|
||||
|
||||
async generateHash() {
|
||||
if (this.password) {
|
||||
if (bcryptRegexp.test(this.password)) { return }
|
||||
@@ -138,11 +142,18 @@ module.exports = class User extends Model {
|
||||
return (result && _.has(result, 'delta') && result.delta === 0)
|
||||
}
|
||||
|
||||
async getPermissions() {
|
||||
const permissions = await this.$relatedQuery('groups').select('permissions').pluck('permissions')
|
||||
this.permissions = _.uniq(_.flatten(permissions))
|
||||
getGlobalPermissions() {
|
||||
return _.uniq(_.flatten(_.map(this.groups, 'permissions')))
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
return _.uniq(_.map(this.groups, 'id'))
|
||||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
// Model Methods
|
||||
// ------------------------------------------------
|
||||
|
||||
static async processProfile(profile) {
|
||||
let primaryEmail = ''
|
||||
if (_.isArray(profile.emails)) {
|
||||
@@ -246,12 +257,17 @@ module.exports = class User extends Model {
|
||||
|
||||
static async refreshToken(user) {
|
||||
if (_.isSafeInteger(user)) {
|
||||
user = await WIKI.models.users.query().findById(user)
|
||||
user = await WIKI.models.users.query().findById(user).eager('groups').modifyEager('groups', builder => {
|
||||
builder.select('groups.id', 'permissions')
|
||||
})
|
||||
if (!user) {
|
||||
WIKI.logger.warn(`Failed to refresh token for user ${user}: Not found.`)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
}
|
||||
} else if(_.isNil(user.groups)) {
|
||||
await user.$relatedQuery('groups').select('groups.id', 'permissions')
|
||||
}
|
||||
|
||||
return {
|
||||
token: jwt.sign({
|
||||
id: user.id,
|
||||
@@ -261,7 +277,8 @@ module.exports = class User extends Model {
|
||||
timezone: user.timezone,
|
||||
localeCode: user.localeCode,
|
||||
defaultEditor: user.defaultEditor,
|
||||
permissions: ['manage:system']
|
||||
permissions: user.getGlobalPermissions(),
|
||||
groups: user.getGroups()
|
||||
}, {
|
||||
key: WIKI.config.certs.private,
|
||||
passphrase: WIKI.config.sessionSecret
|
||||
@@ -398,8 +415,13 @@ module.exports = class User extends Model {
|
||||
}
|
||||
|
||||
static async getGuestUser () {
|
||||
let user = await WIKI.models.users.query().findById(2)
|
||||
user.getPermissions()
|
||||
const user = await WIKI.models.users.query().findById(2).eager('groups').modifyEager('groups', builder => {
|
||||
builder.select('groups.id', 'permissions')
|
||||
})
|
||||
if (!user) {
|
||||
WIKI.logger.error('CRITICAL ERROR: Guest user is missing!')
|
||||
process.exit(1)
|
||||
}
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
@@ -219,9 +219,9 @@ module.exports = () => {
|
||||
})
|
||||
const guestGroup = await WIKI.models.groups.query().insert({
|
||||
name: 'Guests',
|
||||
permissions: JSON.stringify(['read:pages']),
|
||||
permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
|
||||
pageRules: JSON.stringify([
|
||||
{ id: 'guest', roles: ['READ', 'AS_READ', 'CM_READ'], match: 'START', deny: false, path: '', locales: [] }
|
||||
{ id: 'guest', roles: ['read:pages', 'read:assets', 'read:comments'], match: 'START', deny: false, path: '', locales: [] }
|
||||
]),
|
||||
isSystem: true
|
||||
})
|
||||
|
13
server/views/notfound.pug
Normal file
13
server/views/notfound.pug
Normal file
@@ -0,0 +1,13 @@
|
||||
extends master.pug
|
||||
|
||||
block body
|
||||
#root.is-fullscreen
|
||||
v-app
|
||||
.notfound
|
||||
.notfound-content
|
||||
img.animated.fadeIn(src='/svg/icon-delete-file.svg', alt='Not Found')
|
||||
.headline= t('notfound.title')
|
||||
.subheading.mt-3= t('notfound.subtitle')
|
||||
v-btn.mt-5(color='red lighten-4', href='/', large, outline)
|
||||
v-icon(left) home
|
||||
span= t('notfound.gohome')
|
Reference in New Issue
Block a user