From 87f3d26a4750eaf5f56ad50eb49a3b7c6894c2e7 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 25 Mar 2017 23:17:05 -0400 Subject: [PATCH] Setup Wizard - Admin create + config write --- assets/js/configure.js | 2 +- client/js/configure.js | 1 + configure.js | 144 ++++++++++++++++++++++++++++++-------- npm/package.json | 2 +- package.json | 2 +- views/configure/index.pug | 17 ++--- 6 files changed, 127 insertions(+), 41 deletions(-) diff --git a/assets/js/configure.js b/assets/js/configure.js index 943016d5..3f778ea7 100644 --- a/assets/js/configure.js +++ b/assets/js/configure.js @@ -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){}}})}); \ No newline at end of file +"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){}}})}); \ No newline at end of file diff --git a/client/js/configure.js b/client/js/configure.js index 3913a51a..3dff7c9f 100644 --- a/client/js/configure.js +++ b/client/js/configure.js @@ -46,6 +46,7 @@ jQuery(document).ready(function ($) { db: 'mongodb://localhost:27017/wiki', pathData: './data', pathRepo: './repo', + gitUseRemote: true, gitUrl: '', gitBranch: 'master', gitAuthType: 'ssh', diff --git a/configure.js b/configure.js index bc85f3e6..fd800823 100644 --- a/configure.js +++ b/configure.js @@ -51,14 +51,14 @@ module.exports = (port, spinner) => { app.get('*', (req, res) => { let langs = [] + let conf = {} try { langs = yaml.safeLoad(fs.readFileSync('./app/data.yml', 'utf8')).langs + conf = yaml.safeLoad(fs.readFileSync('./config.yml', 'utf8')) } catch (err) { console.error(err) } - res.render('configure/index', { - langs - }) + res.render('configure/index', { langs, conf }) }) /** @@ -164,11 +164,16 @@ module.exports = (port, spinner) => { const dataDir = path.resolve(ROOTPATH, req.body.pathData) const gitDir = path.resolve(ROOTPATH, req.body.pathRepo) - let urlObj = url.parse(req.body.gitUrl) - if (req.body.gitAuthType === 'basic') { - urlObj.auth = req.body.gitAuthUser + ':' + req.body.gitAuthPass + let gitRemoteUrl = '' + console.log(req.body) + + if (req.body.gitUseRemote === true) { + let urlObj = url.parse(req.body.gitUrl) + if (req.body.gitAuthType === 'basic') { + urlObj.auth = req.body.gitAuthUser + ':' + req.body.gitAuthPass + } + gitRemoteUrl = url.format(urlObj) } - const gitRemoteUrl = url.format(urlObj) 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 '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 '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 'Git SSL Verify flag has been set successfully.' }) }, () => { + if (req.body.gitUseRemote === false) { return false } 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 '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 => { if (_.includes(err.message, 'No such remote')) { 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 'Git Pull operation successful.' }) @@ -233,38 +244,115 @@ module.exports = (port, spinner) => { }) /** - * Check the DB connection + * Finalize */ app.post('/finalize', (req, res) => { + const bcrypt = require('bcryptjs-then') + const crypto = Promise.promisifyAll(require('crypto')) let mongo = require('mongodb').MongoClient - mongo.connect(req.body.db, { - autoReconnect: false, - reconnectTries: 2, - reconnectInterval: 1000, - connectTimeoutMS: 5000, - socketTimeoutMS: 5000 - }, (err, db) => { - if (err === null) { - // Try to create a test collection - db.createCollection('test', (err, results) => { + + Promise.join( + new Promise((resolve, reject) => { + mongo.connect(req.body.db, { + autoReconnect: false, + reconnectTries: 2, + reconnectInterval: 1000, + connectTimeoutMS: 5000, + socketTimeoutMS: 5000 + }, (err, db) => { if (err === null) { - // Try to drop test collection - db.dropCollection('test', (err, results) => { + db.createCollection('users', { strict: false }, (err, results) => { if (err === null) { - res.json({ ok: true }) + bcrypt.hash(req.body.adminPassword).then(adminPwdHash => { + 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) { + resolve(true) + } else { + reject(err) + } + db.close() + }) + }) } else { - res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message }) + reject(err) + db.close() } - db.close() }) } else { - res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message }) - db.close() + reject(err) } }) - } else { - res.json({ ok: false, error: err.message }) - } + }), + 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 }) + }).catch(err => { + res.json({ ok: false, error: err.message }) }) }) diff --git a/npm/package.json b/npm/package.json index 4928762c..b571c2a8 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "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", "main": "install.js", "scripts": { diff --git a/package.json b/package.json index cf1c2390..79a8e291 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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", "main": "server.js", "scripts": { diff --git a/views/configure/index.pug b/views/configure/index.pug index b4bd57e0..7f5a99a5 100644 --- a/views/configure/index.pug +++ b/views/configure/index.pug @@ -278,8 +278,8 @@ html .panel-footer .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='proceedToAdmin', 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.is-outlined(v-on:click='conf.gitUseRemote = false; proceedToGitCheck()', v-bind:disabled='loading') Skip this step + button.button.is-indigo(v-on:click='conf.gitUseRemote = true; proceedToGitCheck()', v-bind:disabled='loading || errors.any("git")') Continue //- ============================================== //- GIT CHECK @@ -291,7 +291,7 @@ html span Git Repository Check i(v-if='loading') .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') ul li(v-for='rs in gitcheck.results') #[i.icon-check] {{rs}} @@ -321,18 +321,18 @@ html p.control.is-fullwidth 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 }') - span.desc The full git repository URL to connect to. + span.desc The email address of the administrator account section.columns .column p.control.is-fullwidth label.label Password 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 p.control.is-fullwidth 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" }') - span.desc The git branch to use when synchronizing changes. + span.desc Verify your password again. .panel-footer .progress-bar: div(v-bind:style='{width: currentProgress}') button.button.is-indigo.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back @@ -349,9 +349,6 @@ html i(v-if='loading') .panel-content.is-text 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') i.icon-check 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 }} .panel-footer .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-green(v-on:click='finish', v-if='loading || final.ok', v-bind:disabled='loading') Start