Commit 7e62c01e authored by Nicolas Giard's avatar Nicolas Giard

feat: page Rules access check

parent 75eb2774
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
v-list-tile.pt-2(to='/dashboard') v-list-tile.pt-2(to='/dashboard')
v-list-tile-avatar: v-icon dashboard v-list-tile-avatar: v-icon dashboard
v-list-tile-title {{ $t('admin:dashboard.title') }} v-list-tile-title {{ $t('admin:dashboard.title') }}
template(v-if='hasPermission([`manage:system`, `manage:navigation`, `write:pages`, `manage:pages`, `delete:pages`])')
v-divider.my-2 v-divider.my-2
v-subheader.pl-4 {{ $t('admin:nav.site') }} v-subheader.pl-4 {{ $t('admin:nav.site') }}
v-list-tile(to='/general', v-if='hasPermission(`manage:system`)') v-list-tile(to='/general', v-if='hasPermission(`manage:system`)')
...@@ -18,7 +19,7 @@ ...@@ -18,7 +19,7 @@
v-list-tile(to='/navigation', v-if='hasPermission([`manage:system`, `manage:navigation`])') v-list-tile(to='/navigation', v-if='hasPermission([`manage:system`, `manage:navigation`])')
v-list-tile-avatar: v-icon near_me v-list-tile-avatar: v-icon near_me
v-list-tile-title {{ $t('admin:navigation.title') }} v-list-tile-title {{ $t('admin:navigation.title') }}
v-list-tile(to='/pages') v-list-tile(to='/pages', v-if='hasPermission([`manage:system`, `write:pages`, `manage:pages`, `delete:pages`])')
v-list-tile-avatar: v-icon insert_drive_file v-list-tile-avatar: v-icon insert_drive_file
v-list-tile-title {{ $t('admin:pages.title') }} v-list-tile-title {{ $t('admin:pages.title') }}
v-list-tile-action v-list-tile-action
...@@ -27,15 +28,16 @@ ...@@ -27,15 +28,16 @@
v-list-tile(to='/theme', v-if='hasPermission([`manage:system`, `manage:theme`])') v-list-tile(to='/theme', v-if='hasPermission([`manage:system`, `manage:theme`])')
v-list-tile-avatar: v-icon palette v-list-tile-avatar: v-icon palette
v-list-tile-title {{ $t('admin:theme.title') }} v-list-tile-title {{ $t('admin:theme.title') }}
template(v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`, `manage:users`, `write:users`])')
v-divider.my-2 v-divider.my-2
v-subheader.pl-4 {{ $t('admin:nav.users') }} v-subheader.pl-4 {{ $t('admin:nav.users') }}
v-list-tile(to='/groups') v-list-tile(to='/groups', v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`])')
v-list-tile-avatar: v-icon people v-list-tile-avatar: v-icon people
v-list-tile-title {{ $t('admin:groups.title') }} v-list-tile-title {{ $t('admin:groups.title') }}
v-list-tile-action v-list-tile-action
v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`') v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
.caption.grey--text {{ info.groupsTotal }} .caption.grey--text {{ info.groupsTotal }}
v-list-tile(to='/users') v-list-tile(to='/users', v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`, `manage:users`, `write:users`])')
v-list-tile-avatar: v-icon perm_identity v-list-tile-avatar: v-icon perm_identity
v-list-tile-title {{ $t('admin:users.title') }} v-list-tile-title {{ $t('admin:users.title') }}
v-list-tile-action v-list-tile-action
...@@ -62,8 +64,8 @@ ...@@ -62,8 +64,8 @@
v-list-tile(to='/storage') v-list-tile(to='/storage')
v-list-tile-avatar: v-icon storage v-list-tile-avatar: v-icon storage
v-list-tile-title {{ $t('admin:storage.title') }} v-list-tile-title {{ $t('admin:storage.title') }}
v-divider.my-2
template(v-if='hasPermission([`manage:system`, `manage:api`])') template(v-if='hasPermission([`manage:system`, `manage:api`])')
v-divider.my-2
v-subheader.pl-4 {{ $t('admin:nav.system') }} v-subheader.pl-4 {{ $t('admin:nav.system') }}
v-list-tile(to='/api', v-if='hasPermission([`manage:system`, `manage:api`])') v-list-tile(to='/api', v-if='hasPermission([`manage:system`, `manage:api`])')
v-list-tile-avatar: v-icon call_split v-list-tile-avatar: v-icon call_split
...@@ -74,8 +76,8 @@ ...@@ -74,8 +76,8 @@
v-list-tile(to='/system', v-if='hasPermission(`manage:system`)') v-list-tile(to='/system', v-if='hasPermission(`manage:system`)')
v-list-tile-avatar: v-icon tune v-list-tile-avatar: v-icon tune
v-list-tile-title {{ $t('admin:system.title') }} v-list-tile-title {{ $t('admin:system.title') }}
v-list-tile(to='/utilities', v-if='hasPermission(`manage:system`)') v-list-tile(to='/utilities', v-if='hasPermission(`manage:system`)', disabled)
v-list-tile-avatar: v-icon build v-list-tile-avatar: v-icon(color='grey lighten-2') build
v-list-tile-title {{ $t('admin:utilities.title') }} v-list-tile-title {{ $t('admin:utilities.title') }}
v-list-tile(to='/dev', v-if='hasPermission([`manage:system`, `manage:api`])') v-list-tile(to='/dev', v-if='hasPermission([`manage:system`, `manage:api`])')
v-list-tile-avatar: v-icon weekend v-list-tile-avatar: v-icon weekend
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
:class='isLatestVersion ? "teal lighten-2" : "red lighten-2"' :class='isLatestVersion ? "teal lighten-2" : "red lighten-2"'
dark dark
) )
v-btn(fab, absolute, right, top, small, light, to='system') v-btn(fab, absolute, right, top, small, light, to='system', v-if='hasPermission(`manage:system`)')
v-icon(v-if='isLatestVersion', color='teal') build v-icon(v-if='isLatestVersion', color='teal') build
v-icon(v-else, color='red darken-4') get_app v-icon(v-else, color='red darken-4') get_app
v-card-text v-card-text
...@@ -101,6 +101,7 @@ ...@@ -101,6 +101,7 @@
</template> </template>
<script> <script>
import _ from 'lodash'
import AnimatedNumber from 'animated-number-vue' import AnimatedNumber from 'animated-number-vue'
import { get } from 'vuex-pathify' import { get } from 'vuex-pathify'
...@@ -118,10 +119,20 @@ export default { ...@@ -118,10 +119,20 @@ export default {
isLatestVersion() { isLatestVersion() {
return this.info.currentVersion === this.info.latestVersion return this.info.currentVersion === this.info.latestVersion
}, },
info: get('admin/info') info: get('admin/info'),
permissions: get('user/permissions')
}, },
methods: { methods: {
round(val) { return Math.round(val) } round(val) { return Math.round(val) },
hasPermission(prm) {
if (_.isArray(prm)) {
return _.some(prm, p => {
return _.includes(this.permissions, p)
})
} else {
return _.includes(this.permissions, prm)
}
}
} }
} }
</script> </script>
......
...@@ -78,8 +78,8 @@ ...@@ -78,8 +78,8 @@
dense dense
) )
template(slot='selection', slot-scope='{ item, index }') template(slot='selection', slot-scope='{ item, index }')
v-chip.white--text.ml-0(v-if='index <= 2', small, label, :color='rule.deny ? `red` : `green`').caption {{ item.value }} v-chip.white--text.ml-0(v-if='index <= 1', small, label, :color='rule.deny ? `red` : `green`').caption {{ item.value }}
v-chip.white--text.ml-0(v-if='index === 3', small, label, :color='rule.deny ? `red lighten-2` : `green lighten-2`').caption + {{ rule.roles.length - 3 }} more v-chip.white--text.ml-0(v-if='index === 2', small, label, :color='rule.deny ? `red lighten-2` : `green lighten-2`').caption + {{ rule.roles.length - 2 }} more
template(slot='item', slot-scope='props') template(slot='item', slot-scope='props')
v-list-tile-action(style='min-width: 30px;') v-list-tile-action(style='min-width: 30px;')
v-checkbox( v-checkbox(
...@@ -163,6 +163,26 @@ ...@@ -163,6 +163,26 @@
v-btn(icon, @click='removeRule(rule.id)') v-btn(icon, @click='removeRule(rule.id)')
v-icon(:color='$vuetify.dark ? `grey` : `blue-grey`') clear v-icon(:color='$vuetify.dark ? `grey` : `blue-grey`') clear
v-divider.mt-3
v-subheader.pl-0 Rules Order
.body-1.pl-3 Rules are applied in order of path specificity. A more precise path will always override a less defined path.
.body-1.pl-4 For example, #[span.teal--text /geography/countries] will override #[span.teal--text /geography].
.body-1.pl-3.pt-2 When 2 rules have the same specificity, the priority is given from lowest to highest as follows:
.body-1.pl-3.pt-1
ul
li
strong Path Starts With...
em.caption.pl-1 (lowest)
li
strong Path Ends With...
li
strong Path Matches Regex...
li
strong Path Is Exactly...
em.caption.pl-1 (highest)
.body-1.pl-3.pt-2 When 2 rules have the same path specificity AND the same match type, #[strong.red--text DENY] will always override an #[strong.green--text ALLOW] rule.
</template> </template>
<script> <script>
...@@ -178,16 +198,16 @@ export default { ...@@ -178,16 +198,16 @@ export default {
data() { data() {
return { return {
roles: [ roles: [
{ text: 'Read Pages', value: 'READ', icon: 'insert_drive_file' }, { text: 'Read Pages', value: 'read:pages', icon: 'insert_drive_file' },
{ text: 'Create Pages', value: 'WRITE', icon: 'insert_drive_file' }, { text: 'Create Pages', value: 'write:pages', icon: 'insert_drive_file' },
{ text: 'Edit + Move Pages', value: 'MANAGE', icon: 'insert_drive_file' }, { text: 'Edit + Move Pages', value: 'manage:pages', icon: 'insert_drive_file' },
{ text: 'Delete Pages', value: 'DELETE', icon: 'insert_drive_file' }, { text: 'Delete Pages', value: 'delete:pages', icon: 'insert_drive_file' },
{ text: 'Read / Use Assets', value: 'AS_READ', icon: 'camera' }, { text: 'Read / Use Assets', value: 'read:assets', icon: 'camera' },
{ text: 'Upload Assets', value: 'AS_WRITE', icon: 'camera' }, { text: 'Upload Assets', value: 'write:assets', icon: 'camera' },
{ text: 'Edit + Delete Assets', value: 'AS_MANAGE', icon: 'camera' }, { text: 'Edit + Delete Assets', value: 'manage:assets', icon: 'camera' },
{ text: 'Read Comments', value: 'CM_READ', icon: 'insert_comment' }, { text: 'Read Comments', value: 'read:comments', icon: 'insert_comment' },
{ text: 'Create Comments', value: 'CM_WRITE', icon: 'insert_comment' }, { text: 'Create Comments', value: 'write:comments', icon: 'insert_comment' },
{ text: 'Edit + Delete Comments', value: 'CM_MANAGE', icon: 'insert_comment' } { text: 'Edit + Delete Comments', value: 'manage:comments', icon: 'insert_comment' }
], ],
matches: [ matches: [
{ text: 'Path Starts With...', value: 'START', icon: '/...' }, { text: 'Path Starts With...', value: 'START', icon: '/...' },
......
...@@ -97,13 +97,12 @@ ...@@ -97,13 +97,12 @@
v-btn.btn-animate-rotate(icon, href='/a', slot='activator') v-btn.btn-animate-rotate(icon, href='/a', slot='activator')
v-icon(color='grey') settings v-icon(color='grey') settings
span Admin span Admin
v-menu(offset-y, min-width='300') v-menu(v-if='isAuthenticated', offset-y, min-width='300')
v-tooltip(bottom, slot='activator') v-tooltip(bottom, slot='activator')
v-btn.btn-animate-grow(icon, slot='activator', outline, :color='isAuthenticated ? `blue` : `grey darken-3`') v-btn.btn-animate-grow(icon, slot='activator', outline, color='blue')
v-icon(color='grey') account_circle v-icon(color='grey') account_circle
span Account span Account
v-list.py-0 v-list.py-0
template(v-if='isAuthenticated')
v-list-tile.py-3.grey(avatar, :class='$vuetify.dark ? `darken-4-l5` : `lighten-5`') v-list-tile.py-3.grey(avatar, :class='$vuetify.dark ? `darken-4-l5` : `lighten-5`')
v-list-tile-avatar v-list-tile-avatar
v-avatar.blue(v-if='picture.kind === `initials`', :size='40') v-avatar.blue(v-if='picture.kind === `initials`', :size='40')
...@@ -125,14 +124,11 @@ ...@@ -125,14 +124,11 @@
v-list-tile(@click='logout') v-list-tile(@click='logout')
v-list-tile-action: v-icon(color='red') exit_to_app v-list-tile-action: v-icon(color='red') exit_to_app
v-list-tile-title Logout v-list-tile-title Logout
template(v-else)
v-list-tile(href='/login') v-tooltip(v-else, left)
v-list-tile-action: v-icon(color='grey') person v-btn(icon, slot='activator', outline, color='grey darken-3', href='/login')
v-list-tile-title Login v-icon(color='grey') account_circle
v-divider.my-0 span Login
v-list-tile(href='/register')
v-list-tile-action: v-icon(color='grey') person_add
v-list-tile-title Register
page-selector(mode='create', v-model='newPageModal', :open-handler='pageNewCreate') page-selector(mode='create', v-model='newPageModal', :open-handler='pageNewCreate')
</template> </template>
......
query($locale: String!, $namespace: String!) { query($locale: String!, $namespace: String!) {
localization {
translations(locale:$locale, namespace:$namespace) { translations(locale:$locale, namespace:$namespace) {
key key
value value
} }
}
} }
...@@ -6,7 +6,7 @@ import _ from 'lodash' ...@@ -6,7 +6,7 @@ import _ from 'lodash'
/* global siteConfig, graphQL */ /* global siteConfig, graphQL */
import localeQuery from 'gql/common/common-locale-query.gql' import localeQuery from 'gql/common/common-localization-query-translations.gql'
export default { export default {
VueI18Next, VueI18Next,
...@@ -28,8 +28,8 @@ export default { ...@@ -28,8 +28,8 @@ export default {
} }
}).then(resp => { }).then(resp => {
let ns = {} let ns = {}
if (resp.data.translations.length > 0) { if (_.get(resp, 'data.localization.translations', []).length > 0) {
resp.data.translations.forEach(entry => { resp.data.localization.translations.forEach(entry => {
_.set(ns, entry.key, entry.value) _.set(ns, entry.key, entry.value)
}) })
} }
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
// @import 'node_modules/diff2html/dist/diff2html.min'; // @import 'node_modules/diff2html/dist/diff2html.min';
@import 'pages/new'; @import 'pages/new';
@import 'pages/notfound';
@import 'pages/unauthorized'; @import 'pages/unauthorized';
@import 'pages/welcome'; @import 'pages/welcome';
@import 'pages/error'; @import 'pages/error';
......
.notfound {
background: linear-gradient(to bottom, darken(mc('red', '900'), 25%) 0%, mc('red', '600') 100%);
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: mc('grey', '50');
&::before {
content: '';
display:block;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-image: url('../static/svg/motif-circuit.svg');
background-position: center center;
background-repeat: repeat;
background-size: 200px;
z-index: 0;
opacity: .75;
animation: onboardingBgReveal 80s linear infinite;
@include keyframes(onboardingBgReveal) {
0% {
background-position-y: 0;
}
100% {
background-position-y: -2000px;
}
}
}
&::after {
content: '';
position: absolute;
background-color: transparent;
background-image: url('../static/svg/motif-overlay.svg');
background-attachment: fixed;
background-size: cover;
opacity: .5;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
&-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 2;
}
img {
height: 250px;
margin-bottom: 3rem;
z-index: 2;
animation-duration: 2s;
@include until($tablet) {
height: 200px;
}
}
h1 {
font-size: 1.5rem;
margin-bottom: 1rem;
z-index: 2;
}
h2 {
margin-bottom: 3rem;
z-index: 2;
}
.v-btn {
z-index: 2;
}
}
...@@ -147,6 +147,7 @@ ...@@ -147,6 +147,7 @@
"remove-markdown": "0.3.0", "remove-markdown": "0.3.0",
"request": "2.88.0", "request": "2.88.0",
"request-promise": "4.2.2", "request-promise": "4.2.2",
"safe-regex": "2.0.1",
"scim-query-filter-parser": "1.1.0", "scim-query-filter-parser": "1.1.0",
"semver": "5.6.0", "semver": "5.6.0",
"serve-favicon": "2.5.0", "serve-favicon": "2.5.0",
......
...@@ -131,6 +131,7 @@ router.get('/*', async (req, res, next) => { ...@@ -131,6 +131,7 @@ router.get('/*', async (req, res, next) => {
if (pageArgs.path === 'home') { if (pageArgs.path === 'home') {
return res.redirect('/login') return res.redirect('/login')
} else { } else {
_.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'view'}) return res.render('unauthorized', { action: 'view'})
} }
} }
...@@ -151,7 +152,11 @@ router.get('/*', async (req, res, next) => { ...@@ -151,7 +152,11 @@ router.get('/*', async (req, res, next) => {
res.render('welcome') res.render('welcome')
} else { } else {
_.set(res.locals, 'pageMeta.title', 'Page Not Found') _.set(res.locals, 'pageMeta.title', 'Page Not Found')
if (WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
res.status(404).render('new', { pagePath: req.path }) res.status(404).render('new', { pagePath: req.path })
} else {
res.render('notfound', { action: 'view'})
}
} }
}) })
......
...@@ -15,6 +15,7 @@ module.exports = { ...@@ -15,6 +15,7 @@ module.exports = {
guest: { guest: {
cacheExpiration: moment.utc().subtract(1, 'd') cacheExpiration: moment.utc().subtract(1, 'd')
}, },
groups: {},
/** /**
* Initialize the authentication module * Initialize the authentication module
...@@ -22,22 +23,26 @@ module.exports = { ...@@ -22,22 +23,26 @@ module.exports = {
init() { init() {
this.passport = passport this.passport = passport
passport.serializeUser(function (user, done) { passport.serializeUser((user, done) => {
done(null, user.id) done(null, user.id)
}) })
passport.deserializeUser(function (id, done) { passport.deserializeUser(async (id, done) => {
WIKI.models.users.query().findById(id).then((user) => { try {
const user = await WIKI.models.users.query().findById(id).modifyEager('groups', builder => {
builder.select('groups.id', 'permissions')
})
if (user) { if (user) {
done(null, user) done(null, user)
} else { } else {
done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null) done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
} }
return true } catch (err) {
}).catch((err) => {
done(err, null) done(err, null)
}
}) })
})
this.reloadGroups()
return this return this
}, },
...@@ -117,13 +122,14 @@ module.exports = { ...@@ -117,13 +122,14 @@ module.exports = {
res.cookie('jwt', newToken.token, { expires: moment().add(365, 'days').toDate() }) res.cookie('jwt', newToken.token, { expires: moment().add(365, 'days').toDate() })
} }
} catch (err) { } catch (err) {
WIKI.logger.warn(err)
return next() return next()
} }
} }
// JWT is NOT valid, set as guest // JWT is NOT valid, set as guest
if (!user) { 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 = await WIKI.models.users.getGuestUser()
WIKI.auth.guest.cacheExpiration = moment.utc().add(1, 'm') WIKI.auth.guest.cacheExpiration = moment.utc().add(1, 'm')
} }
...@@ -146,18 +152,99 @@ module.exports = { ...@@ -146,18 +152,99 @@ module.exports = {
* @param {Array<String>} permissions * @param {Array<String>} permissions
* @param {String|Boolean} path * @param {String|Boolean} path
*/ */
checkAccess(user, permissions = [], path = false) { checkAccess(user, permissions = [], page = false) {
// System Admin // System Admin
if (_.includes(user.permissions, 'manage:system')) { if (_.includes(user.permissions, 'manage:system')) {
return true return true
} }
const userPermissions = user.permissions ? user.permissions : user.getGlobalPermissions()
// Check Global Permissions // Check Global Permissions
if (_.intersection(user.permissions, permissions).length < 1) { if (_.intersection(userPermissions, permissions).length < 1) {
return false return false
} }
console.info('---------------------')
// Check Page Rules // 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 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 @@ ...@@ -2,41 +2,41 @@
/* global WIKI */ /* global WIKI */
module.exports = { module.exports = {
Query: { // Query: {
comments(obj, args, context, info) { // comments(obj, args, context, info) {
return WIKI.models.Comment.findAll({ where: args }) // return WIKI.models.Comment.findAll({ where: args })
} // }
}, // },
Mutation: { // Mutation: {
createComment(obj, args) { // createComment(obj, args) {
return WIKI.models.Comment.create({ // return WIKI.models.Comment.create({
content: args.content, // content: args.content,
author: args.userId, // author: args.userId,
document: args.documentId // document: args.documentId
}) // })
}, // },
deleteComment(obj, args) { // deleteComment(obj, args) {
return WIKI.models.Comment.destroy({ // return WIKI.models.Comment.destroy({
where: { // where: {
id: args.id // id: args.id
}, // },
limit: 1 // limit: 1
}) // })
}, // },
modifyComment(obj, args) { // modifyComment(obj, args) {
return WIKI.models.Comment.update({ // return WIKI.models.Comment.update({
content: args.content // content: args.content
}, { // }, {
where: { id: args.id } // where: { id: args.id }
}) // })
} // }
}, // },
Comment: { // Comment: {
author(cm) { // author(cm) {
return cm.getAuthor() // return cm.getAuthor()
}, // },
document(cm) { // document(cm) {
return cm.getDocument() // return cm.getDocument()
} // }
} // }
} }
/* 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 @@ ...@@ -4,48 +4,48 @@
const gql = require('graphql') const gql = require('graphql')
module.exports = { module.exports = {
Query: { // Query: {
files(obj, args, context, info) { // files(obj, args, context, info) {
return WIKI.models.File.findAll({ where: args }) // return WIKI.models.File.findAll({ where: args })
} // }
}, // },
Mutation: { // Mutation: {
uploadFile(obj, args) { // uploadFile(obj, args) {
// todo // // todo
return WIKI.models.File.create(args) // return WIKI.models.File.create(args)
}, // },
deleteFile(obj, args) { // deleteFile(obj, args) {
return WIKI.models.File.destroy({ // return WIKI.models.File.destroy({
where: { // where: {
id: args.id // id: args.id
}, // },
limit: 1 // limit: 1
}) // })
}, // },
renameFile(obj, args) { // renameFile(obj, args) {
return WIKI.models.File.update({ // return WIKI.models.File.update({
filename: args.filename // filename: args.filename
}, { // }, {
where: { id: args.id } // where: { id: args.id }
}) // })
}, // },
moveFile(obj, args) { // moveFile(obj, args) {
return WIKI.models.File.findById(args.fileId).then(fl => { // return WIKI.models.File.findById(args.fileId).then(fl => {
if (!fl) { // if (!fl) {
throw new gql.GraphQLError('Invalid File ID') // throw new gql.GraphQLError('Invalid File ID')
} // }
return WIKI.models.Folder.findById(args.folderId).then(fld => { // return WIKI.models.Folder.findById(args.folderId).then(fld => {
if (!fld) { // if (!fld) {
throw new gql.GraphQLError('Invalid Folder ID') // throw new gql.GraphQLError('Invalid Folder ID')
} // }
return fl.setFolder(fld) // return fl.setFolder(fld)
}) // })
}) // })
} // }
}, // },
File: { // File: {
folder(fl) { // folder(fl) {
return fl.getFolder() // return fl.getFolder()
} // }
} // }
} }
...@@ -2,34 +2,34 @@ ...@@ -2,34 +2,34 @@
/* global WIKI */ /* global WIKI */
module.exports = { module.exports = {
Query: { // Query: {
folders(obj, args, context, info) { // folders(obj, args, context, info) {
return WIKI.models.Folder.findAll({ where: args }) // return WIKI.models.Folder.findAll({ where: args })
} // }
}, // },
Mutation: { // Mutation: {
createFolder(obj, args) { // createFolder(obj, args) {
return WIKI.models.Folder.create(args) // return WIKI.models.Folder.create(args)
}, // },
deleteFolder(obj, args) { // deleteFolder(obj, args) {
return WIKI.models.Folder.destroy({ // return WIKI.models.Folder.destroy({
where: { // where: {
id: args.id // id: args.id
}, // },
limit: 1 // limit: 1
}) // })
}, // },
renameFolder(obj, args) { // renameFolder(obj, args) {
return WIKI.models.Folder.update({ // return WIKI.models.Folder.update({
name: args.name // name: args.name
}, { // }, {
where: { id: args.id } // where: { id: args.id }
}) // })
} // }
}, // },
Folder: { // Folder: {
files(grp) { // files(grp) {
return grp.getFiles() // return grp.getFiles()
} // }
} // }
} }
const graphHelper = require('../../helpers/graph') const graphHelper = require('../../helpers/graph')
const safeRegex = require('safe-regex')
/* global WIKI */ /* global WIKI */
...@@ -44,6 +45,7 @@ module.exports = { ...@@ -44,6 +45,7 @@ module.exports = {
pageRules: JSON.stringify([]), pageRules: JSON.stringify([]),
isSystem: false isSystem: false
}) })
await WIKI.auth.reloadGroups()
return { return {
responseResult: graphHelper.generateSuccess('Group created successfully.'), responseResult: graphHelper.generateSuccess('Group created successfully.'),
group group
...@@ -51,6 +53,7 @@ module.exports = { ...@@ -51,6 +53,7 @@ module.exports = {
}, },
async delete(obj, args) { async delete(obj, args) {
await WIKI.models.groups.query().deleteById(args.id) await WIKI.models.groups.query().deleteById(args.id)
await WIKI.auth.reloadGroups()
return { return {
responseResult: graphHelper.generateSuccess('Group has been deleted.') responseResult: graphHelper.generateSuccess('Group has been deleted.')
} }
...@@ -70,11 +73,20 @@ module.exports = { ...@@ -70,11 +73,20 @@ module.exports = {
} }
}, },
async update(obj, args) { 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({ await WIKI.models.groups.query().patch({
name: args.name, name: args.name,
permissions: JSON.stringify(args.permissions), permissions: JSON.stringify(args.permissions),
pageRules: JSON.stringify(args.pageRules) pageRules: JSON.stringify(args.pageRules)
}).where('id', args.id) }).where('id', args.id)
await WIKI.auth.reloadGroups()
return { return {
responseResult: graphHelper.generateSuccess('Group has been updated.') responseResult: graphHelper.generateSuccess('Group has been updated.')
} }
......
...@@ -31,6 +31,9 @@ module.exports = { ...@@ -31,6 +31,9 @@ module.exports = {
namespacing: WIKI.config.lang.namespacing, namespacing: WIKI.config.lang.namespacing,
namespaces: WIKI.config.lang.namespaces namespaces: WIKI.config.lang.namespaces
} }
},
translations (obj, args, context, info) {
return WIKI.lang.getByNamespace(args.locale, args.namespace)
} }
}, },
LocalizationMutation: { LocalizationMutation: {
......
...@@ -16,15 +16,6 @@ module.exports = { ...@@ -16,15 +16,6 @@ module.exports = {
offsetPage: args.offsetPage || 0, offsetPage: args.offsetPage || 0,
offsetSize: args.offsetSize || 100 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: { PageMutation: {
......
/* 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()
}
}
}
/* 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 = { ...@@ -20,13 +20,9 @@ module.exports = {
Query: { Query: {
async system() { return {} } async system() { return {} }
}, },
Mutation: {
async system() { return {} }
},
SystemQuery: { SystemQuery: {
async info() { return {} } async info() { return {} }
}, },
SystemMutation: { },
SystemInfo: { SystemInfo: {
configFile() { configFile() {
return path.join(process.cwd(), 'config.yml') return path.join(process.cwd(), 'config.yml')
......
...@@ -4,60 +4,60 @@ ...@@ -4,60 +4,60 @@
const gql = require('graphql') const gql = require('graphql')
module.exports = { module.exports = {
Query: { // Query: {
tags(obj, args, context, info) { // tags(obj, args, context, info) {
return WIKI.models.Tag.findAll({ where: args }) // return WIKI.models.Tag.findAll({ where: args })
} // }
}, // },
Mutation: { // Mutation: {
assignTagToDocument(obj, args) { // assignTagToDocument(obj, args) {
return WIKI.models.Tag.findById(args.tagId).then(tag => { // return WIKI.models.Tag.findById(args.tagId).then(tag => {
if (!tag) { // if (!tag) {
throw new gql.GraphQLError('Invalid Tag ID') // throw new gql.GraphQLError('Invalid Tag ID')
} // }
return WIKI.models.Document.findById(args.documentId).then(doc => { // return WIKI.models.Document.findById(args.documentId).then(doc => {
if (!doc) { // if (!doc) {
throw new gql.GraphQLError('Invalid Document ID') // throw new gql.GraphQLError('Invalid Document ID')
} // }
return tag.addDocument(doc) // return tag.addDocument(doc)
}) // })
}) // })
}, // },
createTag(obj, args) { // createTag(obj, args) {
return WIKI.models.Tag.create(args) // return WIKI.models.Tag.create(args)
}, // },
deleteTag(obj, args) { // deleteTag(obj, args) {
return WIKI.models.Tag.destroy({ // return WIKI.models.Tag.destroy({
where: { // where: {
id: args.id // id: args.id
}, // },
limit: 1 // limit: 1
}) // })
}, // },
removeTagFromDocument(obj, args) { // removeTagFromDocument(obj, args) {
return WIKI.models.Tag.findById(args.tagId).then(tag => { // return WIKI.models.Tag.findById(args.tagId).then(tag => {
if (!tag) { // if (!tag) {
throw new gql.GraphQLError('Invalid Tag ID') // throw new gql.GraphQLError('Invalid Tag ID')
} // }
return WIKI.models.Document.findById(args.documentId).then(doc => { // return WIKI.models.Document.findById(args.documentId).then(doc => {
if (!doc) { // if (!doc) {
throw new gql.GraphQLError('Invalid Document ID') // throw new gql.GraphQLError('Invalid Document ID')
} // }
return tag.removeDocument(doc) // return tag.removeDocument(doc)
}) // })
}) // })
}, // },
renameTag(obj, args) { // renameTag(obj, args) {
return WIKI.models.Group.update({ // return WIKI.models.Group.update({
key: args.key // key: args.key
}, { // }, {
where: { id: args.id } // where: { id: args.id }
}) // })
} // }
}, // },
Tag: { // Tag: {
documents(tag) { // documents(tag) {
return tag.getDocuments() // return tag.getDocuments()
} // }
} // }
} }
/* 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 = { ...@@ -22,7 +22,6 @@ module.exports = {
}, },
async single(obj, args, context, info) { async single(obj, args, context, info) {
let usr = await WIKI.models.users.query().findById(args.id) let usr = await WIKI.models.users.query().findById(args.id)
console.info(usr)
usr.password = '' usr.password = ''
usr.tfaSecret = '' usr.tfaSecret = ''
return usr return usr
......
# ====================== #
# Wiki.js GraphQL Schema #
# ENUMS # ====================== #
enum FileType {
binary
image
}
enum RightRole {
read
write
manage
}
# DIRECTIVES # DIRECTIVES
# ----------
directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFINITION directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFINITION
# TYPES # TYPES
# -----
# Generic Key Value Pair
type KeyValuePair { type KeyValuePair {
key: String! key: String!
value: String! value: String!
} }
# General Key Value Pair Input
input KeyValuePairInput { input KeyValuePairInput {
key: String! key: String!
value: String! value: String!
} }
# Generic Mutation Response
type DefaultResponse { type DefaultResponse {
responseResult: ResponseStatus responseResult: ResponseStatus
} }
# Mutation Status
type ResponseStatus { type ResponseStatus {
succeeded: Boolean! succeeded: Boolean!
errorCode: Int! errorCode: Int!
...@@ -39,220 +34,14 @@ type ResponseStatus { ...@@ -39,220 +34,14 @@ type ResponseStatus {
message: String message: String
} }
type Comment { # ROOT
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
}
# Query (Read) # Query (Read)
type Query { 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]
}
# Mutations (Create, Update, Delete) # Mutations (Create, Update, Delete)
type Mutation { 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
}
# Subscriptions (Push, Real-time)
type Subscription type Subscription
...@@ -89,7 +89,7 @@ type PageRule { ...@@ -89,7 +89,7 @@ type PageRule {
id: String! id: String!
deny: Boolean! deny: Boolean!
match: PageRuleMatch! match: PageRuleMatch!
roles: [PageRuleRole]! roles: [String]!
path: String! path: String!
locales: [String]! locales: [String]!
} }
...@@ -98,24 +98,11 @@ input PageRuleInput { ...@@ -98,24 +98,11 @@ input PageRuleInput {
id: String! id: String!
deny: Boolean! deny: Boolean!
match: PageRuleMatch! match: PageRuleMatch!
roles: [PageRuleRole]! roles: [String]!
path: String! path: String!
locales: [String]! locales: [String]!
} }
enum PageRuleRole {
READ
WRITE
MANAGE
DELETE
AS_READ
AS_WRITE
AS_MANAGE
CM_READ
CM_WRITE
CM_MANAGE
}
enum PageRuleMatch { enum PageRuleMatch {
START START
EXACT EXACT
......
...@@ -17,6 +17,7 @@ extend type Mutation { ...@@ -17,6 +17,7 @@ extend type Mutation {
type LocalizationQuery { type LocalizationQuery {
locales: [LocalizationLocale] locales: [LocalizationLocale]
config: LocalizationConfig config: LocalizationConfig
translations(locale: String!, namespace: String!): [Translation]
} }
# ----------------------------------------------- # -----------------------------------------------
...@@ -57,3 +58,8 @@ type LocalizationConfig { ...@@ -57,3 +58,8 @@ type LocalizationConfig {
namespacing: Boolean! namespacing: Boolean!
namespaces: [String]! namespaces: [String]!
} }
type Translation {
key: String!
value: String!
}
...@@ -19,19 +19,7 @@ type PageQuery { ...@@ -19,19 +19,7 @@ type PageQuery {
id: Int! id: Int!
offsetPage: Int offsetPage: Int
offsetSize: Int offsetSize: Int
): PageHistoryResult ): PageHistoryResult @auth(requires: ["manage:system", "read:pages"])
list(
filter: String
orderBy: String
): [PageMinimal]
single(
id: Int
path: String
locale: String
isPrivate: Boolean
): Page
} }
# ----------------------------------------------- # -----------------------------------------------
...@@ -82,21 +70,8 @@ type PageResponse { ...@@ -82,21 +70,8 @@ type PageResponse {
page: Page page: Page
} }
type PageMinimal {
id: Int!
name: String!
userCount: Int
createdAt: Date!
updatedAt: Date!
}
type Page { type Page {
id: Int! id: Int!
name: String!
rights: [Right]
users: [User]
createdAt: Date!
updatedAt: Date!
} }
type PageHistory { type PageHistory {
......
...@@ -6,50 +6,42 @@ extend type Query { ...@@ -6,50 +6,42 @@ extend type Query {
system: SystemQuery system: SystemQuery
} }
extend type Mutation {
system: SystemMutation
}
# ----------------------------------------------- # -----------------------------------------------
# QUERIES # QUERIES
# ----------------------------------------------- # -----------------------------------------------
type SystemQuery { type SystemQuery {
info: SystemInfo @auth(requires: ["manage:system"]) info: SystemInfo
} }
# ----------------------------------------------- # -----------------------------------------------
# MUTATIONS # MUTATIONS
# ----------------------------------------------- # -----------------------------------------------
type SystemMutation {
todo: String
}
# ----------------------------------------------- # -----------------------------------------------
# TYPES # TYPES
# ----------------------------------------------- # -----------------------------------------------
type SystemInfo { type SystemInfo {
configFile: String configFile: String @auth(requires: ["manage:system"])
cpuCores: Int cpuCores: Int @auth(requires: ["manage:system"])
currentVersion: String currentVersion: String @auth(requires: ["manage:system"])
dbHost: String dbHost: String @auth(requires: ["manage:system"])
dbType: String dbType: String @auth(requires: ["manage:system"])
dbVersion: String dbVersion: String @auth(requires: ["manage:system"])
groupsTotal: Int groupsTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:groups", "write:groups", "manage:users", "write:users"])
hostname: String hostname: String @auth(requires: ["manage:system"])
latestVersion: String latestVersion: String @auth(requires: ["manage:system"])
latestVersionReleaseDate: Date latestVersionReleaseDate: Date @auth(requires: ["manage:system"])
nodeVersion: String nodeVersion: String @auth(requires: ["manage:system"])
operatingSystem: String operatingSystem: String @auth(requires: ["manage:system"])
pagesTotal: Int pagesTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:pages", "delete:pages"])
platform: String platform: String @auth(requires: ["manage:system"])
ramTotal: String ramTotal: String @auth(requires: ["manage:system"])
redisHost: String redisHost: String @auth(requires: ["manage:system"])
redisTotalRAM: String redisTotalRAM: String @auth(requires: ["manage:system"])
redisUsedRAM: String redisUsedRAM: String @auth(requires: ["manage:system"])
redisVersion: String redisVersion: String @auth(requires: ["manage:system"])
usersTotal: Int usersTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:groups", "write:groups", "manage:users", "write:users"])
workingDirectory: String workingDirectory: String @auth(requires: ["manage:system"])
} }
...@@ -101,6 +101,10 @@ module.exports = class User extends Model { ...@@ -101,6 +101,10 @@ module.exports = class User extends Model {
await this.generateHash() await this.generateHash()
} }
// ------------------------------------------------
// Instance Methods
// ------------------------------------------------
async generateHash() { async generateHash() {
if (this.password) { if (this.password) {
if (bcryptRegexp.test(this.password)) { return } if (bcryptRegexp.test(this.password)) { return }
...@@ -138,11 +142,18 @@ module.exports = class User extends Model { ...@@ -138,11 +142,18 @@ module.exports = class User extends Model {
return (result && _.has(result, 'delta') && result.delta === 0) return (result && _.has(result, 'delta') && result.delta === 0)
} }
async getPermissions() { getGlobalPermissions() {
const permissions = await this.$relatedQuery('groups').select('permissions').pluck('permissions') return _.uniq(_.flatten(_.map(this.groups, 'permissions')))
this.permissions = _.uniq(_.flatten(permissions)) }
getGroups() {
return _.uniq(_.map(this.groups, 'id'))
} }
// ------------------------------------------------
// Model Methods
// ------------------------------------------------
static async processProfile(profile) { static async processProfile(profile) {
let primaryEmail = '' let primaryEmail = ''
if (_.isArray(profile.emails)) { if (_.isArray(profile.emails)) {
...@@ -246,12 +257,17 @@ module.exports = class User extends Model { ...@@ -246,12 +257,17 @@ module.exports = class User extends Model {
static async refreshToken(user) { static async refreshToken(user) {
if (_.isSafeInteger(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) { if (!user) {
WIKI.logger.warn(`Failed to refresh token for user ${user}: Not found.`) WIKI.logger.warn(`Failed to refresh token for user ${user}: Not found.`)
throw new WIKI.Error.AuthGenericError() throw new WIKI.Error.AuthGenericError()
} }
} else if(_.isNil(user.groups)) {
await user.$relatedQuery('groups').select('groups.id', 'permissions')
} }
return { return {
token: jwt.sign({ token: jwt.sign({
id: user.id, id: user.id,
...@@ -261,7 +277,8 @@ module.exports = class User extends Model { ...@@ -261,7 +277,8 @@ module.exports = class User extends Model {
timezone: user.timezone, timezone: user.timezone,
localeCode: user.localeCode, localeCode: user.localeCode,
defaultEditor: user.defaultEditor, defaultEditor: user.defaultEditor,
permissions: ['manage:system'] permissions: user.getGlobalPermissions(),
groups: user.getGroups()
}, { }, {
key: WIKI.config.certs.private, key: WIKI.config.certs.private,
passphrase: WIKI.config.sessionSecret passphrase: WIKI.config.sessionSecret
...@@ -398,8 +415,13 @@ module.exports = class User extends Model { ...@@ -398,8 +415,13 @@ module.exports = class User extends Model {
} }
static async getGuestUser () { static async getGuestUser () {
let user = await WIKI.models.users.query().findById(2) const user = await WIKI.models.users.query().findById(2).eager('groups').modifyEager('groups', builder => {
user.getPermissions() builder.select('groups.id', 'permissions')
})
if (!user) {
WIKI.logger.error('CRITICAL ERROR: Guest user is missing!')
process.exit(1)
}
return user return user
} }
} }
...@@ -219,9 +219,9 @@ module.exports = () => { ...@@ -219,9 +219,9 @@ module.exports = () => {
}) })
const guestGroup = await WIKI.models.groups.query().insert({ const guestGroup = await WIKI.models.groups.query().insert({
name: 'Guests', name: 'Guests',
permissions: JSON.stringify(['read:pages']), permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
pageRules: JSON.stringify([ 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 isSystem: true
}) })
......
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')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment