feat: page Rules access check
This commit is contained in:
parent
75eb277401
commit
7e62c01ed1
@ -7,40 +7,42 @@
|
||||
v-list-tile.pt-2(to='/dashboard')
|
||||
v-list-tile-avatar: v-icon dashboard
|
||||
v-list-tile-title {{ $t('admin:dashboard.title') }}
|
||||
v-divider.my-2
|
||||
v-subheader.pl-4 {{ $t('admin:nav.site') }}
|
||||
v-list-tile(to='/general', v-if='hasPermission(`manage:system`)')
|
||||
v-list-tile-avatar: v-icon widgets
|
||||
v-list-tile-title {{ $t('admin:general.title') }}
|
||||
v-list-tile(to='/locale', v-if='hasPermission(`manage:system`)')
|
||||
v-list-tile-avatar: v-icon language
|
||||
v-list-tile-title {{ $t('admin:locale.title') }}
|
||||
v-list-tile(to='/navigation', v-if='hasPermission([`manage:system`, `manage:navigation`])')
|
||||
v-list-tile-avatar: v-icon near_me
|
||||
v-list-tile-title {{ $t('admin:navigation.title') }}
|
||||
v-list-tile(to='/pages')
|
||||
v-list-tile-avatar: v-icon insert_drive_file
|
||||
v-list-tile-title {{ $t('admin:pages.title') }}
|
||||
v-list-tile-action
|
||||
v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
|
||||
.caption.grey--text {{ info.pagesTotal }}
|
||||
v-list-tile(to='/theme', v-if='hasPermission([`manage:system`, `manage:theme`])')
|
||||
v-list-tile-avatar: v-icon palette
|
||||
v-list-tile-title {{ $t('admin:theme.title') }}
|
||||
v-divider.my-2
|
||||
v-subheader.pl-4 {{ $t('admin:nav.users') }}
|
||||
v-list-tile(to='/groups')
|
||||
v-list-tile-avatar: v-icon people
|
||||
v-list-tile-title {{ $t('admin:groups.title') }}
|
||||
v-list-tile-action
|
||||
v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
|
||||
.caption.grey--text {{ info.groupsTotal }}
|
||||
v-list-tile(to='/users')
|
||||
v-list-tile-avatar: v-icon perm_identity
|
||||
v-list-tile-title {{ $t('admin:users.title') }}
|
||||
v-list-tile-action
|
||||
v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
|
||||
.caption.grey--text {{ info.usersTotal }}
|
||||
template(v-if='hasPermission([`manage:system`, `manage:navigation`, `write:pages`, `manage:pages`, `delete:pages`])')
|
||||
v-divider.my-2
|
||||
v-subheader.pl-4 {{ $t('admin:nav.site') }}
|
||||
v-list-tile(to='/general', v-if='hasPermission(`manage:system`)')
|
||||
v-list-tile-avatar: v-icon widgets
|
||||
v-list-tile-title {{ $t('admin:general.title') }}
|
||||
v-list-tile(to='/locale', v-if='hasPermission(`manage:system`)')
|
||||
v-list-tile-avatar: v-icon language
|
||||
v-list-tile-title {{ $t('admin:locale.title') }}
|
||||
v-list-tile(to='/navigation', v-if='hasPermission([`manage:system`, `manage:navigation`])')
|
||||
v-list-tile-avatar: v-icon near_me
|
||||
v-list-tile-title {{ $t('admin:navigation.title') }}
|
||||
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-title {{ $t('admin:pages.title') }}
|
||||
v-list-tile-action
|
||||
v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
|
||||
.caption.grey--text {{ info.pagesTotal }}
|
||||
v-list-tile(to='/theme', v-if='hasPermission([`manage:system`, `manage:theme`])')
|
||||
v-list-tile-avatar: v-icon palette
|
||||
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-subheader.pl-4 {{ $t('admin:nav.users') }}
|
||||
v-list-tile(to='/groups', v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`])')
|
||||
v-list-tile-avatar: v-icon people
|
||||
v-list-tile-title {{ $t('admin:groups.title') }}
|
||||
v-list-tile-action
|
||||
v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
|
||||
.caption.grey--text {{ info.groupsTotal }}
|
||||
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-title {{ $t('admin:users.title') }}
|
||||
v-list-tile-action
|
||||
v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
|
||||
.caption.grey--text {{ info.usersTotal }}
|
||||
template(v-if='hasPermission(`manage:system`)')
|
||||
v-divider.my-2
|
||||
v-subheader.pl-4 {{ $t('admin:nav.modules') }}
|
||||
@ -62,8 +64,8 @@
|
||||
v-list-tile(to='/storage')
|
||||
v-list-tile-avatar: v-icon storage
|
||||
v-list-tile-title {{ $t('admin:storage.title') }}
|
||||
v-divider.my-2
|
||||
template(v-if='hasPermission([`manage:system`, `manage:api`])')
|
||||
v-divider.my-2
|
||||
v-subheader.pl-4 {{ $t('admin:nav.system') }}
|
||||
v-list-tile(to='/api', v-if='hasPermission([`manage:system`, `manage:api`])')
|
||||
v-list-tile-avatar: v-icon call_split
|
||||
@ -74,8 +76,8 @@
|
||||
v-list-tile(to='/system', v-if='hasPermission(`manage:system`)')
|
||||
v-list-tile-avatar: v-icon tune
|
||||
v-list-tile-title {{ $t('admin:system.title') }}
|
||||
v-list-tile(to='/utilities', v-if='hasPermission(`manage:system`)')
|
||||
v-list-tile-avatar: v-icon build
|
||||
v-list-tile(to='/utilities', v-if='hasPermission(`manage:system`)', disabled)
|
||||
v-list-tile-avatar: v-icon(color='grey lighten-2') build
|
||||
v-list-tile-title {{ $t('admin:utilities.title') }}
|
||||
v-list-tile(to='/dev', v-if='hasPermission([`manage:system`, `manage:api`])')
|
||||
v-list-tile-avatar: v-icon weekend
|
||||
|
@ -45,7 +45,7 @@
|
||||
:class='isLatestVersion ? "teal lighten-2" : "red lighten-2"'
|
||||
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-else, color='red darken-4') get_app
|
||||
v-card-text
|
||||
@ -101,6 +101,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import AnimatedNumber from 'animated-number-vue'
|
||||
import { get } from 'vuex-pathify'
|
||||
|
||||
@ -118,10 +119,20 @@ export default {
|
||||
isLatestVersion() {
|
||||
return this.info.currentVersion === this.info.latestVersion
|
||||
},
|
||||
info: get('admin/info')
|
||||
info: get('admin/info'),
|
||||
permissions: get('user/permissions')
|
||||
},
|
||||
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>
|
||||
|
@ -78,8 +78,8 @@
|
||||
dense
|
||||
)
|
||||
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 === 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 <= 1', small, label, :color='rule.deny ? `red` : `green`').caption {{ item.value }}
|
||||
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')
|
||||
v-list-tile-action(style='min-width: 30px;')
|
||||
v-checkbox(
|
||||
@ -163,6 +163,26 @@
|
||||
|
||||
v-btn(icon, @click='removeRule(rule.id)')
|
||||
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>
|
||||
|
||||
<script>
|
||||
@ -178,16 +198,16 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
roles: [
|
||||
{ text: 'Read Pages', value: 'READ', icon: 'insert_drive_file' },
|
||||
{ text: 'Create Pages', value: 'WRITE', icon: 'insert_drive_file' },
|
||||
{ text: 'Edit + Move Pages', value: 'MANAGE', icon: 'insert_drive_file' },
|
||||
{ text: 'Delete Pages', value: 'DELETE', icon: 'insert_drive_file' },
|
||||
{ text: 'Read / Use Assets', value: 'AS_READ', icon: 'camera' },
|
||||
{ text: 'Upload Assets', value: 'AS_WRITE', icon: 'camera' },
|
||||
{ text: 'Edit + Delete Assets', value: 'AS_MANAGE', icon: 'camera' },
|
||||
{ text: 'Read Comments', value: 'CM_READ', icon: 'insert_comment' },
|
||||
{ text: 'Create Comments', value: 'CM_WRITE', icon: 'insert_comment' },
|
||||
{ text: 'Edit + Delete Comments', value: 'CM_MANAGE', icon: 'insert_comment' }
|
||||
{ text: 'Read Pages', value: 'read:pages', icon: 'insert_drive_file' },
|
||||
{ text: 'Create Pages', value: 'write:pages', icon: 'insert_drive_file' },
|
||||
{ text: 'Edit + Move Pages', value: 'manage:pages', icon: 'insert_drive_file' },
|
||||
{ text: 'Delete Pages', value: 'delete:pages', icon: 'insert_drive_file' },
|
||||
{ text: 'Read / Use Assets', value: 'read:assets', icon: 'camera' },
|
||||
{ text: 'Upload Assets', value: 'write:assets', icon: 'camera' },
|
||||
{ text: 'Edit + Delete Assets', value: 'manage:assets', icon: 'camera' },
|
||||
{ text: 'Read Comments', value: 'read:comments', icon: 'insert_comment' },
|
||||
{ text: 'Create Comments', value: 'write:comments', icon: 'insert_comment' },
|
||||
{ text: 'Edit + Delete Comments', value: 'manage:comments', icon: 'insert_comment' }
|
||||
],
|
||||
matches: [
|
||||
{ text: 'Path Starts With...', value: 'START', icon: '/...' },
|
||||
|
@ -97,42 +97,38 @@
|
||||
v-btn.btn-animate-rotate(icon, href='/a', slot='activator')
|
||||
v-icon(color='grey') settings
|
||||
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-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
|
||||
span Account
|
||||
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-avatar
|
||||
v-avatar.blue(v-if='picture.kind === `initials`', :size='40')
|
||||
span.white--text.subheading {{picture.initials}}
|
||||
v-avatar(v-else-if='picture.kind === `image`', :size='40')
|
||||
v-img(:src='picture.url')
|
||||
v-list-tile-content
|
||||
v-list-tile-title {{name}}
|
||||
v-list-tile-sub-title {{email}}
|
||||
v-divider.my-0
|
||||
v-list-tile(href='/w')
|
||||
v-list-tile-action: v-icon(color='blue') web
|
||||
v-list-tile-title My Wiki
|
||||
v-divider.my-0
|
||||
v-list-tile(href='/p')
|
||||
v-list-tile-action: v-icon(color='blue') person
|
||||
v-list-tile-title Profile
|
||||
v-divider.my-0
|
||||
v-list-tile(@click='logout')
|
||||
v-list-tile-action: v-icon(color='red') exit_to_app
|
||||
v-list-tile-title Logout
|
||||
template(v-else)
|
||||
v-list-tile(href='/login')
|
||||
v-list-tile-action: v-icon(color='grey') person
|
||||
v-list-tile-title Login
|
||||
v-divider.my-0
|
||||
v-list-tile(href='/register')
|
||||
v-list-tile-action: v-icon(color='grey') person_add
|
||||
v-list-tile-title Register
|
||||
v-list-tile.py-3.grey(avatar, :class='$vuetify.dark ? `darken-4-l5` : `lighten-5`')
|
||||
v-list-tile-avatar
|
||||
v-avatar.blue(v-if='picture.kind === `initials`', :size='40')
|
||||
span.white--text.subheading {{picture.initials}}
|
||||
v-avatar(v-else-if='picture.kind === `image`', :size='40')
|
||||
v-img(:src='picture.url')
|
||||
v-list-tile-content
|
||||
v-list-tile-title {{name}}
|
||||
v-list-tile-sub-title {{email}}
|
||||
v-divider.my-0
|
||||
v-list-tile(href='/w')
|
||||
v-list-tile-action: v-icon(color='blue') web
|
||||
v-list-tile-title My Wiki
|
||||
v-divider.my-0
|
||||
v-list-tile(href='/p')
|
||||
v-list-tile-action: v-icon(color='blue') person
|
||||
v-list-tile-title Profile
|
||||
v-divider.my-0
|
||||
v-list-tile(@click='logout')
|
||||
v-list-tile-action: v-icon(color='red') exit_to_app
|
||||
v-list-tile-title Logout
|
||||
|
||||
v-tooltip(v-else, left)
|
||||
v-btn(icon, slot='activator', outline, color='grey darken-3', href='/login')
|
||||
v-icon(color='grey') account_circle
|
||||
span Login
|
||||
|
||||
page-selector(mode='create', v-model='newPageModal', :open-handler='pageNewCreate')
|
||||
</template>
|
||||
|
@ -1,6 +0,0 @@
|
||||
query($locale: String!, $namespace: String!) {
|
||||
translations(locale:$locale, namespace:$namespace) {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
query($locale: String!, $namespace: String!) {
|
||||
localization {
|
||||
translations(locale:$locale, namespace:$namespace) {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
|
||||
/* global siteConfig, graphQL */
|
||||
|
||||
import localeQuery from 'gql/common/common-locale-query.gql'
|
||||
import localeQuery from 'gql/common/common-localization-query-translations.gql'
|
||||
|
||||
export default {
|
||||
VueI18Next,
|
||||
@ -28,8 +28,8 @@ export default {
|
||||
}
|
||||
}).then(resp => {
|
||||
let ns = {}
|
||||
if (resp.data.translations.length > 0) {
|
||||
resp.data.translations.forEach(entry => {
|
||||
if (_.get(resp, 'data.localization.translations', []).length > 0) {
|
||||
resp.data.localization.translations.forEach(entry => {
|
||||
_.set(ns, entry.key, entry.value)
|
||||
})
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
// @import 'node_modules/diff2html/dist/diff2html.min';
|
||||
|
||||
@import 'pages/new';
|
||||
@import 'pages/notfound';
|
||||
@import 'pages/unauthorized';
|
||||
@import 'pages/welcome';
|
||||
@import 'pages/error';
|
||||
|
81
client/scss/pages/_notfound.scss
Normal file
81
client/scss/pages/_notfound.scss
Normal file
@ -0,0 +1,81 @@
|
||||
.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 @@
|
||||
"remove-markdown": "0.3.0",
|
||||
"request": "2.88.0",
|
||||
"request-promise": "4.2.2",
|
||||
"safe-regex": "2.0.1",
|
||||
"scim-query-filter-parser": "1.1.0",
|
||||
"semver": "5.6.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
|
@ -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')
|
Loading…
Reference in New Issue
Block a user