Setup Wizard - Admin create + config write

This commit is contained in:
NGPixel 2017-03-25 23:17:05 -04:00
parent 6698ca094d
commit 87f3d26a47
6 changed files with 127 additions and 41 deletions

View File

@ -1 +1 @@
"use strict";Vue.use(VeeValidate,{enableAutoClasses:!0,classNames:{touched:"is-touched",untouched:"is-untouched",valid:"is-valid",invalid:"is-invalid",pristine:"is-pristine",dirty:"is-dirty"}}),jQuery(document).ready(function(t){new Vue({el:"main",data:{loading:!1,state:"welcome",syscheck:{ok:!1,error:"",results:[]},dbcheck:{ok:!1,error:""},gitcheck:{ok:!1,error:""},final:{ok:!1,error:"",results:[]},conf:{title:"Wiki",host:"http://",port:80,lang:"en",db:"mongodb://localhost:27017/wiki",pathData:"./data",pathRepo:"./repo",gitUrl:"",gitBranch:"master",gitAuthType:"ssh",gitAuthSSHKey:"",gitAuthUser:"",gitAuthPass:"",gitAuthSSL:!0,gitSignatureName:"",gitSignatureEmail:"",adminEmail:"",adminPassword:"",adminPasswordConfirm:""},considerations:{https:!1,port:!1,localhost:!1}},computed:{currentProgress:function(){var t="0%";switch(this.state){case"welcome":t="0%";break;case"syscheck":t=this.syscheck.ok?"15%":"5%";break;case"general":t="20%";break;case"considerations":t="30%";break;case"db":t="35%";break;case"dbcheck":t=this.dbcheck.ok?"50%":"40%";break;case"paths":t="55%";break;case"git":t="60%";break;case"gitcheck":t=this.gitcheck.ok?"75%":"65%";break;case"admin":t="80%"}return t}},methods:{proceedToWelcome:function(t){this.state="welcome",this.loading=!1},proceedToSyscheck:function(t){var e=this;this.state="syscheck",this.loading=!0,e.syscheck={ok:!1,error:"",results:[]},_.delay(function(){axios.post("/syscheck").then(function(t){t.data.ok===!0?(e.syscheck.ok=!0,e.syscheck.results=t.data.results):(e.syscheck.ok=!1,e.syscheck.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},proceedToGeneral:function(t){var e=this;e.state="general",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("general")})},proceedToConsiderations:function(t){this.considerations={https:!_.startsWith(this.conf.host,"https"),port:!1,localhost:_.includes(this.conf.host,"localhost")},this.state="considerations",this.loading=!1},proceedToDb:function(t){var e=this;e.state="db",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("db")})},proceedToDbcheck:function(t){var e=this;this.state="dbcheck",this.loading=!0,e.dbcheck={ok:!1,error:""},_.delay(function(){axios.post("/dbcheck",{db:e.conf.db}).then(function(t){t.data.ok===!0?e.dbcheck.ok=!0:(e.dbcheck.ok=!1,e.dbcheck.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},proceedToPaths:function(t){var e=this;e.state="paths",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("paths")})},proceedToGit:function(t){var e=this;e.state="git",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("git")})},proceedToGitCheck:function(t){var e=this;this.state="gitcheck",this.loading=!0,e.gitcheck={ok:!1,results:[],error:""},_.delay(function(){axios.post("/gitcheck",e.conf).then(function(t){t.data.ok===!0?(e.gitcheck.ok=!0,e.gitcheck.results=t.data.results):(e.gitcheck.ok=!1,e.gitcheck.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},proceedToAdmin:function(t){var e=this;e.state="admin",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("admin")})},proceedToFinal:function(t){var e=this;e.state="final",e.loading=!0,e.final={ok:!1,error:"",results:[]},_.delay(function(){axios.post("/finalize",e.conf).then(function(t){t.data.ok===!0?(e.final.ok=!0,e.final.results=t.data.results):(e.final.ok=!1,e.final.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},finish:function(t){}}})}); "use strict";Vue.use(VeeValidate,{enableAutoClasses:!0,classNames:{touched:"is-touched",untouched:"is-untouched",valid:"is-valid",invalid:"is-invalid",pristine:"is-pristine",dirty:"is-dirty"}}),jQuery(document).ready(function(t){new Vue({el:"main",data:{loading:!1,state:"welcome",syscheck:{ok:!1,error:"",results:[]},dbcheck:{ok:!1,error:""},gitcheck:{ok:!1,error:""},final:{ok:!1,error:"",results:[]},conf:{title:"Wiki",host:"http://",port:80,lang:"en",db:"mongodb://localhost:27017/wiki",pathData:"./data",pathRepo:"./repo",gitUseRemote:!0,gitUrl:"",gitBranch:"master",gitAuthType:"ssh",gitAuthSSHKey:"",gitAuthUser:"",gitAuthPass:"",gitAuthSSL:!0,gitSignatureName:"",gitSignatureEmail:"",adminEmail:"",adminPassword:"",adminPasswordConfirm:""},considerations:{https:!1,port:!1,localhost:!1}},computed:{currentProgress:function(){var t="0%";switch(this.state){case"welcome":t="0%";break;case"syscheck":t=this.syscheck.ok?"15%":"5%";break;case"general":t="20%";break;case"considerations":t="30%";break;case"db":t="35%";break;case"dbcheck":t=this.dbcheck.ok?"50%":"40%";break;case"paths":t="55%";break;case"git":t="60%";break;case"gitcheck":t=this.gitcheck.ok?"75%":"65%";break;case"admin":t="80%"}return t}},methods:{proceedToWelcome:function(t){this.state="welcome",this.loading=!1},proceedToSyscheck:function(t){var e=this;this.state="syscheck",this.loading=!0,e.syscheck={ok:!1,error:"",results:[]},_.delay(function(){axios.post("/syscheck").then(function(t){t.data.ok===!0?(e.syscheck.ok=!0,e.syscheck.results=t.data.results):(e.syscheck.ok=!1,e.syscheck.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},proceedToGeneral:function(t){var e=this;e.state="general",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("general")})},proceedToConsiderations:function(t){this.considerations={https:!_.startsWith(this.conf.host,"https"),port:!1,localhost:_.includes(this.conf.host,"localhost")},this.state="considerations",this.loading=!1},proceedToDb:function(t){var e=this;e.state="db",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("db")})},proceedToDbcheck:function(t){var e=this;this.state="dbcheck",this.loading=!0,e.dbcheck={ok:!1,error:""},_.delay(function(){axios.post("/dbcheck",{db:e.conf.db}).then(function(t){t.data.ok===!0?e.dbcheck.ok=!0:(e.dbcheck.ok=!1,e.dbcheck.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},proceedToPaths:function(t){var e=this;e.state="paths",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("paths")})},proceedToGit:function(t){var e=this;e.state="git",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("git")})},proceedToGitCheck:function(t){var e=this;this.state="gitcheck",this.loading=!0,e.gitcheck={ok:!1,results:[],error:""},_.delay(function(){axios.post("/gitcheck",e.conf).then(function(t){t.data.ok===!0?(e.gitcheck.ok=!0,e.gitcheck.results=t.data.results):(e.gitcheck.ok=!1,e.gitcheck.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},proceedToAdmin:function(t){var e=this;e.state="admin",e.loading=!1,e.$nextTick(function(){e.$validator.validateAll("admin")})},proceedToFinal:function(t){var e=this;e.state="final",e.loading=!0,e.final={ok:!1,error:"",results:[]},_.delay(function(){axios.post("/finalize",e.conf).then(function(t){t.data.ok===!0?(e.final.ok=!0,e.final.results=t.data.results):(e.final.ok=!1,e.final.error=t.data.error),e.loading=!1,e.$nextTick()}).catch(function(t){window.alert(t.message)})},1e3)},finish:function(t){}}})});

View File

@ -46,6 +46,7 @@ jQuery(document).ready(function ($) {
db: 'mongodb://localhost:27017/wiki', db: 'mongodb://localhost:27017/wiki',
pathData: './data', pathData: './data',
pathRepo: './repo', pathRepo: './repo',
gitUseRemote: true,
gitUrl: '', gitUrl: '',
gitBranch: 'master', gitBranch: 'master',
gitAuthType: 'ssh', gitAuthType: 'ssh',

View File

@ -51,14 +51,14 @@ module.exports = (port, spinner) => {
app.get('*', (req, res) => { app.get('*', (req, res) => {
let langs = [] let langs = []
let conf = {}
try { try {
langs = yaml.safeLoad(fs.readFileSync('./app/data.yml', 'utf8')).langs langs = yaml.safeLoad(fs.readFileSync('./app/data.yml', 'utf8')).langs
conf = yaml.safeLoad(fs.readFileSync('./config.yml', 'utf8'))
} catch (err) { } catch (err) {
console.error(err) console.error(err)
} }
res.render('configure/index', { res.render('configure/index', { langs, conf })
langs
})
}) })
/** /**
@ -164,11 +164,16 @@ module.exports = (port, spinner) => {
const dataDir = path.resolve(ROOTPATH, req.body.pathData) const dataDir = path.resolve(ROOTPATH, req.body.pathData)
const gitDir = path.resolve(ROOTPATH, req.body.pathRepo) const gitDir = path.resolve(ROOTPATH, req.body.pathRepo)
let gitRemoteUrl = ''
console.log(req.body)
if (req.body.gitUseRemote === true) {
let urlObj = url.parse(req.body.gitUrl) let urlObj = url.parse(req.body.gitUrl)
if (req.body.gitAuthType === 'basic') { if (req.body.gitAuthType === 'basic') {
urlObj.auth = req.body.gitAuthUser + ':' + req.body.gitAuthPass urlObj.auth = req.body.gitAuthUser + ':' + req.body.gitAuthPass
} }
const gitRemoteUrl = url.format(urlObj) gitRemoteUrl = url.format(urlObj)
}
Promise.mapSeries([ Promise.mapSeries([
() => { () => {
@ -183,21 +188,25 @@ module.exports = (port, spinner) => {
}) })
}, },
() => { () => {
if (req.body.gitUseRemote === false) { return false }
return exec.stdout('git', ['config', '--local', 'user.name', req.body.gitSignatureName], { cwd: gitDir }).then(result => { return exec.stdout('git', ['config', '--local', 'user.name', req.body.gitSignatureName], { cwd: gitDir }).then(result => {
return 'Git Signature Name has been set successfully.' return 'Git Signature Name has been set successfully.'
}) })
}, },
() => { () => {
if (req.body.gitUseRemote === false) { return false }
return exec.stdout('git', ['config', '--local', 'user.email', req.body.gitSignatureEmail], { cwd: gitDir }).then(result => { return exec.stdout('git', ['config', '--local', 'user.email', req.body.gitSignatureEmail], { cwd: gitDir }).then(result => {
return 'Git Signature Name has been set successfully.' return 'Git Signature Name has been set successfully.'
}) })
}, },
() => { () => {
if (req.body.gitUseRemote === false) { return false }
return exec.stdout('git', ['config', '--local', '--bool', 'http.sslVerify', req.body.gitAuthSSL], { cwd: gitDir }).then(result => { return exec.stdout('git', ['config', '--local', '--bool', 'http.sslVerify', req.body.gitAuthSSL], { cwd: gitDir }).then(result => {
return 'Git SSL Verify flag has been set successfully.' return 'Git SSL Verify flag has been set successfully.'
}) })
}, },
() => { () => {
if (req.body.gitUseRemote === false) { return false }
if (req.body.gitAuthType === 'ssh') { if (req.body.gitAuthType === 'ssh') {
return exec.stdout('git', ['config', '--local', 'core.sshCommand', 'ssh -i "' + req.body.gitAuthSSHKey + '" -o StrictHostKeyChecking=no'], { cwd: gitDir }).then(result => { return exec.stdout('git', ['config', '--local', 'core.sshCommand', 'ssh -i "' + req.body.gitAuthSSHKey + '" -o StrictHostKeyChecking=no'], { cwd: gitDir }).then(result => {
return 'Git SSH Private Key path has been set successfully.' return 'Git SSH Private Key path has been set successfully.'
@ -207,6 +216,7 @@ module.exports = (port, spinner) => {
} }
}, },
() => { () => {
if (req.body.gitUseRemote === false) { return false }
return exec.stdout('git', ['remote', 'remove', 'origin'], { cwd: gitDir }).catch(err => { return exec.stdout('git', ['remote', 'remove', 'origin'], { cwd: gitDir }).catch(err => {
if (_.includes(err.message, 'No such remote')) { if (_.includes(err.message, 'No such remote')) {
return true return true
@ -220,6 +230,7 @@ module.exports = (port, spinner) => {
}) })
}, },
() => { () => {
if (req.body.gitUseRemote === false) { return false }
return exec.stdout('git', ['pull', 'origin', req.body.gitBranch], { cwd: gitDir }).then(result => { return exec.stdout('git', ['pull', 'origin', req.body.gitBranch], { cwd: gitDir }).then(result => {
return 'Git Pull operation successful.' return 'Git Pull operation successful.'
}) })
@ -233,10 +244,15 @@ module.exports = (port, spinner) => {
}) })
/** /**
* Check the DB connection * Finalize
*/ */
app.post('/finalize', (req, res) => { app.post('/finalize', (req, res) => {
const bcrypt = require('bcryptjs-then')
const crypto = Promise.promisifyAll(require('crypto'))
let mongo = require('mongodb').MongoClient let mongo = require('mongodb').MongoClient
Promise.join(
new Promise((resolve, reject) => {
mongo.connect(req.body.db, { mongo.connect(req.body.db, {
autoReconnect: false, autoReconnect: false,
reconnectTries: 2, reconnectTries: 2,
@ -245,26 +261,98 @@ module.exports = (port, spinner) => {
socketTimeoutMS: 5000 socketTimeoutMS: 5000
}, (err, db) => { }, (err, db) => {
if (err === null) { if (err === null) {
// Try to create a test collection db.createCollection('users', { strict: false }, (err, results) => {
db.createCollection('test', (err, results) => {
if (err === null) { if (err === null) {
// Try to drop test collection bcrypt.hash(req.body.adminPassword).then(adminPwdHash => {
db.dropCollection('test', (err, results) => { db.collection('users').findOneAndUpdate({
provider: 'local',
email: req.body.adminEmail
}, {
provider: 'local',
email: req.body.adminEmail,
name: 'Administrator',
password: adminPwdHash,
rights: [{
role: 'admin',
path: '/',
exact: false,
deny: false
}],
updatedAt: new Date(),
createdAt: new Date()
}, {
upsert: true,
returnOriginal: false
}, (err, results) => {
if (err === null) { if (err === null) {
resolve(true)
} else {
reject(err)
}
db.close()
})
})
} else {
reject(err)
db.close()
}
})
} else {
reject(err)
}
})
}),
fs.readFileAsync('./config.yml', 'utf8').then(confRaw => {
let conf = yaml.safeLoad(confRaw)
conf.title = req.body.title
conf.host = req.body.host
conf.port = req.body.port
conf.paths = {
repo: req.body.pathRepo,
data: req.body.pathData
}
conf.uploads = {
maxImageFileSize: (conf.uploads && _.isNumber(conf.uploads.maxImageFileSize)) ? conf.uploads.maxImageFileSize : 3,
maxOtherFileSize: (conf.uploads && _.isNumber(conf.uploads.maxOtherFileSize)) ? conf.uploads.maxOtherFileSize : 100
}
conf.lang = req.body.lang
conf.public = (conf.public === true)
if (conf.auth && conf.auth.local) {
conf.auth.local = { enabled: true }
} else {
conf.auth = { local: { enabled: true } }
}
conf.admin = req.body.adminEmail
conf.db = req.body.db
if (req.body.gitUseRemote === false) {
conf.git = false
} else {
conf.git = {
url: req.body.gitUrl,
branch: req.body.gitBranch,
auth: {
type: req.body.gitAuthType,
username: req.body.gitAuthUser,
password: req.body.gitAuthPass,
privateKey: req.body.gitAuthSSHKey,
sslVerify: (req.body.gitAuthSSL === true)
},
signature: {
name: req.body.gitSignatureName,
email: req.body.gitSignatureEmail
}
}
}
return crypto.randomBytesAsync(32).then(buf => {
conf.sessionSecret = buf.toString('hex')
confRaw = yaml.safeDump(conf)
return fs.writeFileAsync('./config.yml', confRaw)
})
})
).then(() => {
res.json({ ok: true }) res.json({ ok: true })
} else { }).catch(err => {
res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
}
db.close()
})
} else {
res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
db.close()
}
})
} else {
res.json({ ok: false, error: err.message }) res.json({ ok: false, error: err.message })
}
}) })
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "wiki.js", "name": "wiki.js",
"version": "1.0.0-beta.8.1", "version": "1.0.0-beta.9",
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown", "description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
"main": "install.js", "main": "install.js",
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
{ {
"name": "wiki", "name": "wiki",
"version": "1.0.0-beta.8", "version": "1.0.0-beta.9",
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown", "description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {

View File

@ -278,8 +278,8 @@ html
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-indigo.is-outlined(v-on:click='proceedToPaths', v-bind:disabled='loading') Back button.button.is-indigo.is-outlined(v-on:click='proceedToPaths', v-bind:disabled='loading') Back
button.button.is-indigo.is-outlined(v-on:click='proceedToAdmin', v-bind:disabled='loading') Skip this step button.button.is-indigo.is-outlined(v-on:click='conf.gitUseRemote = false; proceedToGitCheck()', v-bind:disabled='loading') Skip this step
button.button.is-indigo(v-on:click='proceedToGitCheck', v-bind:disabled='loading || errors.any("git")') Continue button.button.is-indigo(v-on:click='conf.gitUseRemote = true; proceedToGitCheck()', v-bind:disabled='loading || errors.any("git")') Continue
//- ============================================== //- ==============================================
//- GIT CHECK //- GIT CHECK
@ -291,7 +291,7 @@ html
span Git Repository Check span Git Repository Check
i(v-if='loading') i(v-if='loading')
.panel-content.is-text .panel-content.is-text
p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Testing the connection to Git repository... p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Verifying Git repository settings...
p(v-if='!loading && gitcheck.ok') p(v-if='!loading && gitcheck.ok')
ul ul
li(v-for='rs in gitcheck.results') #[i.icon-check] {{rs}} li(v-for='rs in gitcheck.results') #[i.icon-check] {{rs}}
@ -321,18 +321,18 @@ html
p.control.is-fullwidth p.control.is-fullwidth
label.label Administrator Email label.label Administrator Email
input(type='text', placeholder='e.g. admin@example.com', v-model='conf.adminEmail', data-vv-scope='admin', name='ipt-adminemail', v-validate='{ required: true, email: true }') input(type='text', placeholder='e.g. admin@example.com', v-model='conf.adminEmail', data-vv-scope='admin', name='ipt-adminemail', v-validate='{ required: true, email: true }')
span.desc The full git repository URL to connect to. span.desc The email address of the administrator account
section.columns section.columns
.column .column
p.control.is-fullwidth p.control.is-fullwidth
label.label Password label.label Password
input(type='password', v-model='conf.adminPassword', data-vv-scope='admin', name='ipt-adminpwd', v-validate='{ required: true, min: 8 }') input(type='password', v-model='conf.adminPassword', data-vv-scope='admin', name='ipt-adminpwd', v-validate='{ required: true, min: 8 }')
span.desc The full git repository URL to connect to. span.desc At least 8 characters long.
.column .column
p.control.is-fullwidth p.control.is-fullwidth
label.label Confirm Password label.label Confirm Password
input(type='password', v-model='conf.adminPasswordConfirm', data-vv-scope='admin', name='ipt-adminpwd2', v-validate='{ required: true, confirmed: "ipt-adminpwd" }') input(type='password', v-model='conf.adminPasswordConfirm', data-vv-scope='admin', name='ipt-adminpwd2', v-validate='{ required: true, confirmed: "ipt-adminpwd" }')
span.desc The git branch to use when synchronizing changes. span.desc Verify your password again.
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-indigo.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back button.button.is-indigo.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back
@ -349,9 +349,6 @@ html
i(v-if='loading') i(v-if='loading')
.panel-content.is-text .panel-content.is-text
p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Finalizing your installation... p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Finalizing your installation...
p(v-if='!loading && final.ok')
ul
li(v-for='rs in final.results') #[i.icon-check] {{rs}}
p(v-if='!loading && final.ok') p(v-if='!loading && final.ok')
i.icon-check i.icon-check
strong Wiki.js was configured successfully and is now ready for use. strong Wiki.js was configured successfully and is now ready for use.
@ -360,7 +357,7 @@ html
p(v-if='!loading && !final.ok') #[i.icon-square-cross] Error: {{ final.error }} p(v-if='!loading && !final.ok') #[i.icon-square-cross] Error: {{ final.error }}
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-indigo.is-outlined(v-on:click='proceedToWelcome', v-bind:disabled='loading') Back button.button.is-indigo.is-outlined(v-on:click='proceedToAdmin', v-bind:disabled='loading') Back
button.button.is-teal(v-on:click='proceedToFinal', v-if='!loading && !final.ok') Try Again button.button.is-teal(v-on:click='proceedToFinal', v-if='!loading && !final.ok') Try Again
button.button.is-green(v-on:click='finish', v-if='loading || final.ok', v-bind:disabled='loading') Start button.button.is-green(v-on:click='finish', v-if='loading || final.ok', v-bind:disabled='loading') Start