feat: import content + x-forwarded toggle

This commit is contained in:
Nick 2019-10-05 16:58:34 -04:00
parent fddde494e2
commit 5b9dd43e96
9 changed files with 196 additions and 40 deletions

View File

@ -163,6 +163,7 @@
persistent-hint persistent-hint
hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.' hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.'
) )
v-divider.mt-3 v-divider.mt-3
v-switch( v-switch(
inset inset
@ -173,6 +174,16 @@
hint='Limits the referrer header to same origin.' hint='Limits the referrer header to same origin.'
) )
v-divider.mt-3
v-switch(
inset
label='Trust X-Forwarded-* Proxy Headers'
color='red darken-2'
v-model='config.securityTrustProxy'
persistent-hint
hint='Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.'
)
v-divider.mt-3 v-divider.mt-3
v-switch( v-switch(
inset inset
@ -250,6 +261,7 @@ export default {
featureTinyPNG: false, featureTinyPNG: false,
securityIframe: true, securityIframe: true,
securityReferrerPolicy: true, securityReferrerPolicy: true,
securityTrustProxy: true,
securityHSTS: false, securityHSTS: false,
securityHSTSDuration: 0, securityHSTSDuration: 0,
securityCSP: false, securityCSP: false,
@ -296,6 +308,7 @@ export default {
featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false), featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false),
securityIframe: _.get(this.config, 'securityIframe', false), securityIframe: _.get(this.config, 'securityIframe', false),
securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false), securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
securityTrustProxy: _.get(this.config, 'securityTrustProxy', false),
securityHSTS: _.get(this.config, 'securityHSTS', false), securityHSTS: _.get(this.config, 'securityHSTS', false),
securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0), securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0),
securityCSP: _.get(this.config, 'securityCSP', false), securityCSP: _.get(this.config, 'securityCSP', false),

View File

@ -18,7 +18,7 @@
v-icon(color='red') mdi-trash-can-outline v-icon(color='red') mdi-trash-can-outline
v-card v-card
.dialog-header.is-red Delete Group? .dialog-header.is-red Delete Group?
v-card-text Are you sure you want to delete group #[strong {{ group.name }}]? All users will be unassigned from this group. v-card-text.pa-4 Are you sure you want to delete group #[strong {{ group.name }}]? All users will be unassigned from this group.
v-card-actions v-card-actions
v-spacer v-spacer
v-btn(text, @click='deleteGroupDialog = false') Cancel v-btn(text, @click='deleteGroupDialog = false') Cancel

View File

@ -63,7 +63,7 @@
v-col(v-if='gitAuthMode === `ssh`', cols='12') v-col(v-if='gitAuthMode === `ssh`', cols='12')
v-textarea( v-textarea(
outlined outlined
label='Private Key' label='Private Key Contents'
placeholder='-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----' placeholder='-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----'
hide-details hide-details
v-model='gitPrivKey' v-model='gitPrivKey'
@ -72,7 +72,7 @@
v-col(cols='6') v-col(cols='6')
v-text-field( v-text-field(
label='Username' label='Username'
v-model='gitUserEmail' v-model='gitUsername'
outlined outlined
hide-details hide-details
) )
@ -80,7 +80,7 @@
v-text-field( v-text-field(
type='password' type='password'
label='Password / PAT' label='Password / PAT'
v-model='gitUserName' v-model='gitPassword'
outlined outlined
hide-details hide-details
) )
@ -108,10 +108,14 @@
outlined outlined
hide-details hide-details
) )
.caption.mt-2 This folder should be empty or not exist yet. #[strong.deep-orange--text.text--darken-2 DO NOT] point to your existing Wiki.js 1.x repository folder. In most cases, it should be left to the default value.
v-alert(color='deep-orange', outlined, icon='mdi-alert', prominent)
.body-2 - Note that if you already configured the git storage module, its configuration will be replaced with the above.
.body-2 - Although both v1 and v2 installations can use the same remote git repository, you shouldn't make edits to the same pages simultaneously.
v-radio-group(v-model='contentMode', hide-details) v-radio-group(v-model='contentMode', hide-details)
v-divider v-divider
v-radio.mt-3( v-radio.mt-3(
value='local' value='disk'
color='primary' color='primary'
) )
template(v-slot:label) template(v-slot:label)
@ -152,7 +156,7 @@
template(v-slot:label) template(v-slot:label)
div div
span Create groups for each unique user permissions configuration span Create groups for each unique user permissions configuration
.caption: em Note that this can result in a large amount of groups being created. .caption: em #[strong.primary--text Recommended] | Users having identical permission sets will be assigned to the same group. Note that this can potentially result in a large amount of groups being created.
v-divider v-divider
v-radio.mt-3( v-radio.mt-3(
value='SINGLE' value='SINGLE'
@ -161,7 +165,7 @@
template(v-slot:label) template(v-slot:label)
div div
span Create a single group with all imported users span Create a single group with all imported users
.caption: em #[strong.primary--text Recommended] | The new group will have read permissions enabled by default. .caption: em The new group will have read permissions enabled by default.
v-divider v-divider
v-radio.mt-3( v-radio.mt-3(
value='NONE' value='NONE'
@ -172,6 +176,10 @@
span Don't create any group span Don't create any group
.caption: em Users will not be able to access your wiki until they are assigned to a group. .caption: em Users will not be able to access your wiki until they are assigned to a group.
v-alert.mt-5(color='deep-orange', outlined, icon='mdi-alert', prominent)
.body-2 Note that any user that already exists in this installation will not be imported. A list of skipped users will be displayed upon completion.
.caption.grey--text You must first delete from this installation any user you want to migrate over from the old installation.
v-card-chin v-card-chin
v-btn.px-3(depressed, color='deep-orange darken-2', :disabled='!wantUsers && !wantContent', @click='startImport').ml-0 v-btn.px-3(depressed, color='deep-orange darken-2', :disabled='!wantUsers && !wantContent', @click='startImport').ml-0
v-icon(left, color='white') mdi-database-import v-icon(left, color='white') mdi-database-import
@ -220,9 +228,6 @@
v-icon(left) mdi-alert v-icon(left) mdi-alert
span {{failedUsers.length}} failed span {{failedUsers.length}} failed
.body-2 #[strong {{successGroups}}] groups created .body-2 #[strong {{successGroups}}] groups created
template(v-if='wantContent')
.body-2 #[strong {{successPages}}] pages
.body-2 #[strong {{successAssets}}] assets
v-card-actions.green.darken-1 v-card-actions.green.darken-1
v-spacer v-spacer
v-btn.px-5( v-btn.px-5(
@ -266,6 +271,10 @@ import _ from 'lodash'
import { SemipolarSpinner } from 'epic-spinners' import { SemipolarSpinner } from 'epic-spinners'
import utilityImportv1UsersMutation from 'gql/admin/utilities/utilities-mutation-importv1-users.gql' import utilityImportv1UsersMutation from 'gql/admin/utilities/utilities-mutation-importv1-users.gql'
import storageTargetsQuery from 'gql/admin/storage/storage-query-targets.gql'
import storageStatusQuery from 'gql/admin/storage/storage-query-status.gql'
import targetExecuteActionMutation from 'gql/admin/storage/storage-mutation-executeaction.gql'
import targetsSaveMutation from 'gql/admin/storage/storage-mutation-save-targets.gql'
export default { export default {
components: { components: {
@ -274,7 +283,7 @@ export default {
data() { data() {
return { return {
importFilters: ['content', 'users'], importFilters: ['content', 'users'],
groupMode: 'SINGLE', groupMode: 'MULTI',
contentMode: 'git', contentMode: 'git',
dbConnStr: 'mongodb://', dbConnStr: 'mongodb://',
contentPath: '/wiki-v1/repo', contentPath: '/wiki-v1/repo',
@ -289,14 +298,14 @@ export default {
gitRepoUrl: '', gitRepoUrl: '',
gitRepoBranch: 'master', gitRepoBranch: 'master',
gitPrivKey: '', gitPrivKey: '',
gitUsername: '',
gitPassword: '',
gitUserEmail: '', gitUserEmail: '',
gitUserName: '', gitUserName: '',
gitRepoPath: './data/repo', gitRepoPath: './data/repo',
progress: 0, progress: 0,
successUsers: 0, successUsers: 0,
successPages: 0, successPages: 0,
successGroups: 0,
successAssets: 0,
showFailedUsers: false, showFailedUsers: false,
failedUsers: [] failedUsers: []
} }
@ -321,40 +330,161 @@ export default {
this.progress = 0 this.progress = 0
this.failedUsers = [] this.failedUsers = []
// -> Import Users _.delay(async () => {
// -> Import Users
if (this.wantUsers) { if (this.wantUsers) {
try { try {
const resp = await this.$apollo.mutate({ const resp = await this.$apollo.mutate({
mutation: utilityImportv1UsersMutation, mutation: utilityImportv1UsersMutation,
variables: { variables: {
mongoDbConnString: this.dbConnStr, mongoDbConnString: this.dbConnStr,
groupMode: this.groupMode groupMode: this.groupMode
}
})
const respObj = _.get(resp, 'data.system.importUsersFromV1', {})
if (!_.get(respObj, 'responseResult.succeeded', false)) {
throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occured'))
} }
}) this.successUsers = _.get(respObj, 'usersCount', 0)
const respObj = _.get(resp, 'data.system.importUsersFromV1', {}) this.successGroups = _.get(respObj, 'groupsCount', 0)
if (!_.get(respObj, 'responseResult.succeeded', false)) { this.failedUsers = _.get(respObj, 'failed', [])
throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occured')) this.progress += 50
} catch (err) {
this.$store.commit('pushGraphError', err)
this.isLoading = false
return
} }
this.successUsers = _.get(respObj, 'usersCount', 0)
this.successGroups = _.get(respObj, 'groupsCount', 0)
this.failedUsers = _.get(respObj, 'failed', [])
this.progress += 50
} catch (err) {
this.$store.commit('pushGraphError', err)
this.isLoading = false
return
} }
}
// -> Import Content // -> Import Content
if (this.wantContent) { if (this.wantContent) {
try {
const resp = await this.$apollo.query({
query: storageTargetsQuery,
fetchPolicy: 'network-only'
})
if (_.has(resp, 'data.storage.targets')) {
this.progress += 10
let targets = resp.data.storage.targets.map(str => {
let nStr = {
...str,
config: _.sortBy(str.config.map(cfg => ({
...cfg,
value: JSON.parse(cfg.value)
})), [t => t.value.order])
}
} // -> Setup Git Module
this.isLoading = false if (this.contentMode === 'git' && nStr.key === 'git') {
this.isSuccess = true nStr.isEnabled = true
nStr.mode = 'sync'
nStr.syncInterval = 'PT5M'
nStr.config = [
{ key: 'authType', value: { value: this.gitAuthMode } },
{ key: 'repoUrl', value: { value: this.gitRepoUrl } },
{ key: 'branch', value: { value: this.gitRepoBranch } },
{ key: 'sshPrivateKeyMode', value: { value: 'contents' } },
{ key: 'sshPrivateKeyPath', value: { value: '' } },
{ key: 'sshPrivateKeyContent', value: { value: this.gitPrivKey } },
{ key: 'verifySSL', value: { value: this.gitVerifySSL } },
{ key: 'basicUsername', value: { value: this.gitUsername } },
{ key: 'basicPassword', value: { value: this.gitPassword } },
{ key: 'defaultEmail', value: { value: this.gitUserEmail } },
{ key: 'defaultName', value: { value: this.gitUserName } },
{ key: 'localRepoPath', value: { value: this.gitRepoPath } },
{ key: 'gitBinaryPath', value: { value: '' } }
]
}
return nStr
})
// -> Save storage modules configuration
const respSv = await this.$apollo.mutate({
mutation: targetsSaveMutation,
variables: {
targets: targets.map(tgt => _.pick(tgt, [
'isEnabled',
'key',
'config',
'mode',
'syncInterval'
])).map(str => ({...str, config: str.config.map(cfg => ({...cfg, value: JSON.stringify({ v: cfg.value.value })}))}))
}
})
const respObj = _.get(respSv, 'data.storage.updateTargets', {})
if (!_.get(respObj, 'responseResult.succeeded', false)) {
throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occured'))
}
this.progress += 10
// -> Wait for success sync
let statusAttempts = 0
while (statusAttempts < 10) {
statusAttempts++
const respStatus = await this.$apollo.query({
query: storageStatusQuery,
fetchPolicy: 'network-only'
})
if (_.has(respStatus, 'data.storage.status[0]')) {
const st = _.find(respStatus.data.storage.status, ['key', this.contentMode])
if (!st) {
throw new Error('Storage target could not be configured.')
}
switch (st.status) {
case 'pending':
if (statusAttempts >= 10) {
throw new Error('Storage target is stuck in pending state. Try again.')
} else {
continue
}
case 'operational':
statusAttempts = 10
break
case 'error':
throw new Error(st.message)
}
} else {
throw new Error('Failed to fetch storage sync status.')
}
}
this.progress += 15
// -> Perform import all
const respImport = await this.$apollo.mutate({
mutation: targetExecuteActionMutation,
variables: {
targetKey: this.contentMode,
handler: 'importAll'
}
})
const respImportObj = _.get(respImport, 'data.storage.executeAction', {})
if (!_.get(respImportObj, 'responseResult.succeeded', false)) {
throw new Error(_.get(respImportObj, 'responseResult.message', 'An unexpected error occured'))
}
this.progress += 15
} else {
throw new Error('Failed to fetch storage targets.')
}
} catch (err) {
this.$store.commit('pushGraphError', err)
this.isLoading = false
return
}
}
this.isLoading = false
this.isSuccess = true
}, 1500)
} }
} }
} }

View File

@ -13,6 +13,7 @@ mutation (
$featurePersonalWikis: Boolean! $featurePersonalWikis: Boolean!
$securityIframe: Boolean! $securityIframe: Boolean!
$securityReferrerPolicy: Boolean! $securityReferrerPolicy: Boolean!
$securityTrustProxy: Boolean!
$securityHSTS: Boolean! $securityHSTS: Boolean!
$securityHSTSDuration: Int! $securityHSTSDuration: Int!
$securityCSP: Boolean! $securityCSP: Boolean!
@ -34,6 +35,7 @@ mutation (
featurePersonalWikis: $featurePersonalWikis, featurePersonalWikis: $featurePersonalWikis,
securityIframe: $securityIframe, securityIframe: $securityIframe,
securityReferrerPolicy: $securityReferrerPolicy, securityReferrerPolicy: $securityReferrerPolicy,
securityTrustProxy: $securityTrustProxy,
securityHSTS: $securityHSTS, securityHSTS: $securityHSTS,
securityHSTSDuration: $securityHSTSDuration, securityHSTSDuration: $securityHSTSDuration,
securityCSP: $securityCSP, securityCSP: $securityCSP,

View File

@ -15,6 +15,7 @@
featurePersonalWikis featurePersonalWikis
securityIframe securityIframe
securityReferrerPolicy securityReferrerPolicy
securityTrustProxy
securityHSTS securityHSTS
securityHSTSDuration securityHSTSDuration
securityCSP securityCSP

View File

@ -45,6 +45,7 @@ defaults:
security: security:
securityIframe: true securityIframe: true
securityReferrerPolicy: true securityReferrerPolicy: true
securityTrustProxy: true
securityHSTS: false securityHSTS: false
securityHSTSDuration: 300 securityHSTSDuration: 300
securityCSP: false securityCSP: false

View File

@ -46,6 +46,7 @@ module.exports = {
WIKI.config.security = { WIKI.config.security = {
securityIframe: args.securityIframe, securityIframe: args.securityIframe,
securityReferrerPolicy: args.securityReferrerPolicy, securityReferrerPolicy: args.securityReferrerPolicy,
securityTrustProxy: args.securityTrustProxy,
securityHSTS: args.securityHSTS, securityHSTS: args.securityHSTS,
securityHSTSDuration: args.securityHSTSDuration, securityHSTSDuration: args.securityHSTSDuration,
securityCSP: args.securityCSP, securityCSP: args.securityCSP,
@ -53,6 +54,12 @@ module.exports = {
} }
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'seo', 'logo', 'features', 'security']) await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'seo', 'logo', 'features', 'security'])
if (WIKI.config.security.securityTrustProxy) {
WIKI.app.enable('trust proxy')
} else {
WIKI.app.disable('trust proxy')
}
return { return {
responseResult: graphHelper.generateSuccess('Site configuration updated successfully') responseResult: graphHelper.generateSuccess('Site configuration updated successfully')
} }

View File

@ -38,6 +38,7 @@ type SiteMutation {
featurePersonalWikis: Boolean! featurePersonalWikis: Boolean!
securityIframe: Boolean! securityIframe: Boolean!
securityReferrerPolicy: Boolean! securityReferrerPolicy: Boolean!
securityTrustProxy: Boolean!
securityHSTS: Boolean! securityHSTS: Boolean!
securityHSTSDuration: Int! securityHSTSDuration: Int!
securityCSP: Boolean! securityCSP: Boolean!
@ -64,6 +65,7 @@ type SiteConfig {
featurePersonalWikis: Boolean! featurePersonalWikis: Boolean!
securityIframe: Boolean! securityIframe: Boolean!
securityReferrerPolicy: Boolean! securityReferrerPolicy: Boolean!
securityTrustProxy: Boolean!
securityHSTS: Boolean! securityHSTS: Boolean!
securityHSTSDuration: Int! securityHSTSDuration: Int!
securityCSP: Boolean! securityCSP: Boolean!

View File

@ -48,7 +48,7 @@ module.exports = async () => {
app.use(mw.security) app.use(mw.security)
app.use(cors(WIKI.config.cors)) app.use(cors(WIKI.config.cors))
app.options('*', cors(WIKI.config.cors)) app.options('*', cors(WIKI.config.cors))
if (WIKI.config.trustProxy) { if (WIKI.config.security.securityTrustProxy) {
app.enable('trust proxy') app.enable('trust proxy')
} }