Commit 414dc386 authored by NGPixel's avatar NGPixel

Standard JS code conversion + fixes

parent a508b2a7
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- Change log
### Fixed
- Fixed issue with social accounts with empty name
### Changed
- Updated dependencies + snyk policy
- Conversion to Standard JS compliant code
## [v1.0-beta.2] - 2017-01-30
### Added
- Save own profile under My Account
### Changed
- Updated dependencies + snyk policy
[Unreleased]: https://github.com/Requarks/wiki/compare/v1.0-beta.2...HEAD
[v1.0-beta.2]: https://github.com/Requarks/wiki/releases/tag/v1.0-beta.2
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
"use strict"; 'use strict'
jQuery( document ).ready(function( $ ) {
jQuery(document).ready(function ($) {
// ==================================== // ====================================
// Scroll // Scroll
// ==================================== // ====================================
$('a').smoothScroll({ $('a').smoothScroll({
speed: 400, speed: 400,
offset: -70 offset: -70
}); })
var sticky = new Sticky('.stickyscroll'); var sticky = new Sticky('.stickyscroll')
// ==================================== // ====================================
// Notifications // Notifications
// ==================================== // ====================================
$(window).bind('beforeunload', () => { $(window).bind('beforeunload', () => {
$('#notifload').addClass('active'); $('#notifload').addClass('active')
}); })
$(document).ajaxSend(() => { $(document).ajaxSend(() => {
$('#notifload').addClass('active'); $('#notifload').addClass('active')
}).ajaxComplete(() => { }).ajaxComplete(() => {
$('#notifload').removeClass('active'); $('#notifload').removeClass('active')
}); })
var alerts = new Alerts(); var alerts = new Alerts()
if(alertsData) { if (alertsData) {
_.forEach(alertsData, (alertRow) => { _.forEach(alertsData, (alertRow) => {
alerts.push(alertRow); alerts.push(alertRow)
}); })
} }
// ==================================== // ====================================
// Establish WebSocket connection // Establish WebSocket connection
// ==================================== // ====================================
var socket = io(window.location.origin); var socket = io(window.location.origin)
//=include components/search.js // =include components/search.js
// ==================================== // ====================================
// Pages logic // Pages logic
// ==================================== // ====================================
//=include pages/view.js // =include pages/view.js
//=include pages/create.js // =include pages/create.js
//=include pages/edit.js // =include pages/edit.js
//=include pages/source.js // =include pages/source.js
//=include pages/admin.js // =include pages/admin.js
})
});
//=include helpers/form.js // =include helpers/form.js
//=include helpers/pages.js // =include helpers/pages.js
//=include components/alerts.js // =include components/alerts.js
\ No newline at end of file
"use strict"; 'use strict'
/** /**
* Alerts * Alerts
...@@ -10,25 +10,23 @@ class Alerts { ...@@ -10,25 +10,23 @@ class Alerts {
* *
* @class * @class
*/ */
constructor() { constructor () {
let self = this
let self = this;
self.mdl = new Vue({
self.mdl = new Vue({ el: '#alerts',
el: '#alerts', data: {
data: { children: []
children: [] },
}, methods: {
methods: { acknowledge: (uid) => {
acknowledge: (uid) => { self.close(uid)
self.close(uid); }
} }
} })
});
self.uidNext = 1
self.uidNext = 1; }
}
/** /**
* Show a new Alert * Show a new Alert
...@@ -36,29 +34,27 @@ class Alerts { ...@@ -36,29 +34,27 @@ class Alerts {
* @param {Object} options Alert properties * @param {Object} options Alert properties
* @return {null} Void * @return {null} Void
*/ */
push(options) { push (options) {
let self = this
let self = this;
let nAlert = _.defaults(options, {
_uid: self.uidNext,
class: 'info',
message: '---',
sticky: false,
title: '---'
});
self.mdl.children.push(nAlert); let nAlert = _.defaults(options, {
_uid: self.uidNext,
class: 'info',
message: '---',
sticky: false,
title: '---'
})
if(!nAlert.sticky) { self.mdl.children.push(nAlert)
_.delay(() => {
self.close(nAlert._uid);
}, 5000);
}
self.uidNext++; if (!nAlert.sticky) {
_.delay(() => {
self.close(nAlert._uid)
}, 5000)
}
} self.uidNext++
}
/** /**
* Shorthand method for pushing errors * Shorthand method for pushing errors
...@@ -66,14 +62,14 @@ class Alerts { ...@@ -66,14 +62,14 @@ class Alerts {
* @param {String} title The title * @param {String} title The title
* @param {String} message The message * @param {String} message The message
*/ */
pushError(title, message) { pushError (title, message) {
this.push({ this.push({
class: 'error', class: 'error',
message, message,
sticky: false, sticky: false,
title title
}); })
} }
/** /**
* Shorthand method for pushing success messages * Shorthand method for pushing success messages
...@@ -81,35 +77,33 @@ class Alerts { ...@@ -81,35 +77,33 @@ class Alerts {
* @param {String} title The title * @param {String} title The title
* @param {String} message The message * @param {String} message The message
*/ */
pushSuccess(title, message) { pushSuccess (title, message) {
this.push({ this.push({
class: 'success', class: 'success',
message, message,
sticky: false, sticky: false,
title title
}); })
} }
/** /**
* Close an alert * Close an alert
* *
* @param {Integer} uid The unique ID of the alert * @param {Integer} uid The unique ID of the alert
*/ */
close(uid) { close (uid) {
let self = this
let self = this;
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid])
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid]); let nAlert = _.nth(self.mdl.children, nAlertIdx)
let nAlert = _.nth(self.mdl.children, nAlertIdx);
if (nAlertIdx >= 0 && nAlert) {
if(nAlertIdx >= 0 && nAlert) { nAlert.class += ' exit'
nAlert.class += ' exit'; Vue.set(self.mdl.children, nAlertIdx, nAlert)
Vue.set(self.mdl.children, nAlertIdx, nAlert); _.delay(() => {
_.delay(() => { self.mdl.children.splice(nAlertIdx, 1)
self.mdl.children.splice(nAlertIdx, 1); }, 500)
}, 500); }
} }
} }
}
\ No newline at end of file
let modelist = ace.require("ace/ext/modelist"); let modelist = ace.require('ace/ext/modelist')
let codeEditor = null; let codeEditor = null
// ACE - Mode Loader // ACE - Mode Loader
let modelistLoaded = []; let modelistLoaded = []
let loadAceMode = (m) => { let loadAceMode = (m) => {
return $.ajax({ return $.ajax({
url: '/js/ace/mode-' + m + '.js', url: '/js/ace/mode-' + m + '.js',
dataType: "script", dataType: 'script',
cache: true, cache: true,
beforeSend: () => { beforeSend: () => {
if(_.includes(modelistLoaded, m)) { if (_.includes(modelistLoaded, m)) {
return false; return false
} }
}, },
success: () => { success: () => {
modelistLoaded.push(m); modelistLoaded.push(m)
} }
}); })
}; }
// Vue Code Block instance // Vue Code Block instance
let vueCodeBlock = new Vue({ let vueCodeBlock = new Vue({
el: '#modal-editor-codeblock', el: '#modal-editor-codeblock',
data: { data: {
modes: modelist.modesByName, modes: modelist.modesByName,
modeSelected: 'text', modeSelected: 'text',
initContent: '' initContent: ''
}, },
watch: { watch: {
modeSelected: (val, oldVal) => { modeSelected: (val, oldVal) => {
loadAceMode(val).done(() => { loadAceMode(val).done(() => {
ace.require("ace/mode/" + val); ace.require('ace/mode/' + val)
codeEditor.getSession().setMode("ace/mode/" + val); codeEditor.getSession().setMode('ace/mode/' + val)
}); })
} }
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-editor-codeblock').addClass('is-active')
$('#modal-editor-codeblock').addClass('is-active'); _.delay(() => {
codeEditor = ace.edit('codeblock-editor')
codeEditor.setTheme('ace/theme/tomorrow_night')
codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected)
codeEditor.setOption('fontSize', '14px')
codeEditor.setOption('hScrollBarAlwaysVisible', false)
codeEditor.setOption('wrap', true)
_.delay(() => { codeEditor.setValue(vueCodeBlock.initContent)
codeEditor = ace.edit("codeblock-editor");
codeEditor.setTheme("ace/theme/tomorrow_night");
codeEditor.getSession().setMode("ace/mode/" + vueCodeBlock.modeSelected);
codeEditor.setOption('fontSize', '14px');
codeEditor.setOption('hScrollBarAlwaysVisible', false);
codeEditor.setOption('wrap', true);
codeEditor.setValue(vueCodeBlock.initContent); codeEditor.focus()
codeEditor.renderer.updateFull()
}, 300)
},
cancel: (ev) => {
mdeModalOpenState = false
$('#modal-editor-codeblock').removeClass('is-active')
vueCodeBlock.initContent = ''
},
insertCode: (ev) => {
if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection')
}
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
codeEditor.focus(); mde.codemirror.doc.replaceSelection(codeBlockText)
codeEditor.renderer.updateFull(); vueCodeBlock.cancel()
}, 300); }
}
}, })
cancel: (ev) => {
mdeModalOpenState = false;
$('#modal-editor-codeblock').removeClass('is-active');
vueCodeBlock.initContent = '';
},
insertCode: (ev) => {
if(mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection');
}
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n';
mde.codemirror.doc.replaceSelection(codeBlockText);
vueCodeBlock.cancel();
}
}
});
\ No newline at end of file
const videoRules = { const videoRules = {
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'), 'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'), 'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i') 'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
}; }
// Vue Video instance // Vue Video instance
let vueVideo = new Vue({ let vueVideo = new Vue({
el: '#modal-editor-video', el: '#modal-editor-video',
data: { data: {
link: '' link: ''
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-editor-video').addClass('is-active'); $('#modal-editor-video').addClass('is-active')
$('#modal-editor-video input').focus(); $('#modal-editor-video input').focus()
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-video').removeClass('is-active'); $('#modal-editor-video').removeClass('is-active')
vueVideo.link = ''; vueVideo.link = ''
}, },
insertVideo: (ev) => { insertVideo: (ev) => {
if (mde.codemirror.doc.somethingSelected()) {
if(mde.codemirror.doc.somethingSelected()) { mde.codemirror.execCommand('singleSelection')
mde.codemirror.execCommand('singleSelection'); }
}
// Guess video type // Guess video type
let videoType = _.findKey(videoRules, (vr) => { let videoType = _.findKey(videoRules, (vr) => {
return vr.test(vueVideo.link); return vr.test(vueVideo.link)
}); })
if(_.isNil(videoType)) { if (_.isNil(videoType)) {
videoType = 'video'; videoType = 'video'
} }
// Insert video tag // Insert video tag
let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'; let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'
mde.codemirror.doc.replaceSelection(videoText); mde.codemirror.doc.replaceSelection(videoText)
vueVideo.cancel(); vueVideo.cancel()
}
} }
} })
});
\ No newline at end of file
...@@ -3,215 +3,210 @@ ...@@ -3,215 +3,210 @@
// Markdown Editor // Markdown Editor
// ==================================== // ====================================
if($('#mk-editor').length === 1) { if ($('#mk-editor').length === 1) {
let mdeModalOpenState = false
let mdeModalOpenState = false; let mdeCurrentEditor = null
let mdeCurrentEditor = null;
Vue.filter('filesize', (v) => {
Vue.filter('filesize', (v) => { return _.toUpper(filesize(v))
return _.toUpper(filesize(v)); })
});
// =include editor-image.js
//=include editor-image.js // =include editor-file.js
//=include editor-file.js // =include editor-video.js
//=include editor-video.js // =include editor-codeblock.js
//=include editor-codeblock.js
var mde = new SimpleMDE({
var mde = new SimpleMDE({ autofocus: true,
autofocus: true, autoDownloadFontAwesome: false,
autoDownloadFontAwesome: false, element: $('#mk-editor').get(0),
element: $("#mk-editor").get(0), placeholder: 'Enter Markdown formatted content here...',
placeholder: 'Enter Markdown formatted content here...', spellChecker: false,
spellChecker: false, status: false,
status: false, toolbar: [{
toolbar: [{ name: 'bold',
name: "bold", action: SimpleMDE.toggleBold,
action: SimpleMDE.toggleBold, className: 'icon-bold',
className: "icon-bold", title: 'Bold'
title: "Bold", },
}, {
{ name: 'italic',
name: "italic", action: SimpleMDE.toggleItalic,
action: SimpleMDE.toggleItalic, className: 'icon-italic',
className: "icon-italic", title: 'Italic'
title: "Italic", },
}, {
{ name: 'strikethrough',
name: "strikethrough", action: SimpleMDE.toggleStrikethrough,
action: SimpleMDE.toggleStrikethrough, className: 'icon-strikethrough',
className: "icon-strikethrough", title: 'Strikethrough'
title: "Strikethrough", },
}, '|',
'|', {
{ name: 'heading-1',
name: "heading-1", action: SimpleMDE.toggleHeading1,
action: SimpleMDE.toggleHeading1, className: 'icon-header fa-header-x fa-header-1',
className: "icon-header fa-header-x fa-header-1", title: 'Big Heading'
title: "Big Heading", },
}, {
{ name: 'heading-2',
name: "heading-2", action: SimpleMDE.toggleHeading2,
action: SimpleMDE.toggleHeading2, className: 'icon-header fa-header-x fa-header-2',
className: "icon-header fa-header-x fa-header-2", title: 'Medium Heading'
title: "Medium Heading", },
}, {
{ name: 'heading-3',
name: "heading-3", action: SimpleMDE.toggleHeading3,
action: SimpleMDE.toggleHeading3, className: 'icon-header fa-header-x fa-header-3',
className: "icon-header fa-header-x fa-header-3", title: 'Small Heading'
title: "Small Heading", },
}, {
{ name: 'quote',
name: "quote", action: SimpleMDE.toggleBlockquote,
action: SimpleMDE.toggleBlockquote, className: 'icon-quote-left',
className: "icon-quote-left", title: 'Quote'
title: "Quote", },
}, '|',
'|', {
{ name: 'unordered-list',
name: "unordered-list", action: SimpleMDE.toggleUnorderedList,
action: SimpleMDE.toggleUnorderedList, className: 'icon-th-list',
className: "icon-th-list", title: 'Bullet List'
title: "Bullet List", },
}, {
{ name: 'ordered-list',
name: "ordered-list", action: SimpleMDE.toggleOrderedList,
action: SimpleMDE.toggleOrderedList, className: 'icon-list-ol',
className: "icon-list-ol", title: 'Numbered List'
title: "Numbered List", },
}, '|',
'|', {
{ name: 'link',
name: "link", action: (editor) => {
action: (editor) => { /* if(!mdeModalOpenState) {
/*if(!mdeModalOpenState) {
mdeModalOpenState = true; mdeModalOpenState = true;
$('#modal-editor-link').slideToggle(); $('#modal-editor-link').slideToggle();
}*/ } */
}, },
className: "icon-link2", className: 'icon-link2',
title: "Insert Link", title: 'Insert Link'
}, },
{ {
name: "image", name: 'image',
action: (editor) => { action: (editor) => {
if(!mdeModalOpenState) { if (!mdeModalOpenState) {
vueImage.open(); vueImage.open()
} }
}, },
className: "icon-image", className: 'icon-image',
title: "Insert Image", title: 'Insert Image'
}, },
{ {
name: "file", name: 'file',
action: (editor) => { action: (editor) => {
if(!mdeModalOpenState) { if (!mdeModalOpenState) {
vueFile.open(); vueFile.open()
} }
}, },
className: "icon-paper", className: 'icon-paper',
title: "Insert File", title: 'Insert File'
}, },
{ {
name: "video", name: 'video',
action: (editor) => { action: (editor) => {
if(!mdeModalOpenState) { if (!mdeModalOpenState) {
vueVideo.open(); vueVideo.open()
} }
}, },
className: "icon-video-camera2", className: 'icon-video-camera2',
title: "Insert Video Player", title: 'Insert Video Player'
}, },
'|', '|',
{ {
name: "inline-code", name: 'inline-code',
action: (editor) => { action: (editor) => {
if (!editor.codemirror.doc.somethingSelected()) {
if(!editor.codemirror.doc.somethingSelected()) { return alerts.pushError('Invalid selection', 'You must select at least 1 character first.')
return alerts.pushError('Invalid selection','You must select at least 1 character first.'); }
} let curSel = editor.codemirror.doc.getSelections()
let curSel = editor.codemirror.doc.getSelections(); curSel = _.map(curSel, (s) => {
curSel = _.map(curSel, (s) => { return '`' + s + '`'
return '`' + s + '`'; })
}); editor.codemirror.doc.replaceSelections(curSel)
editor.codemirror.doc.replaceSelections(curSel); },
className: 'icon-terminal',
}, title: 'Inline Code'
className: "icon-terminal", },
title: "Inline Code", {
}, name: 'code-block',
{ action: (editor) => {
name: "code-block", if (!mdeModalOpenState) {
action: (editor) => { mdeModalOpenState = true
if(!mdeModalOpenState) {
mdeModalOpenState = true; if (mde.codemirror.doc.somethingSelected()) {
vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
if(mde.codemirror.doc.somethingSelected()) { }
vueCodeBlock.initContent = mde.codemirror.doc.getSelection();
} vueCodeBlock.open()
}
vueCodeBlock.open(); },
className: 'icon-code',
} title: 'Code Block'
}, },
className: "icon-code", '|',
title: "Code Block", {
}, name: 'table',
'|', action: (editor) => {
{ // todo
name: "table", },
action: (editor) => { className: 'icon-table',
//todo title: 'Insert Table'
}, },
className: "icon-table", {
title: "Insert Table", name: 'horizontal-rule',
}, action: SimpleMDE.drawHorizontalRule,
{ className: 'icon-minus2',
name: "horizontal-rule", title: 'Horizontal Rule'
action: SimpleMDE.drawHorizontalRule, }
className: "icon-minus2", ],
title: "Horizontal Rule", shortcuts: {
} 'toggleBlockquote': null,
], 'toggleFullScreen': null
shortcuts: { }
"toggleBlockquote": null, })
"toggleFullScreen": null
} // -> Save
});
let saveCurrentDocument = (ev) => {
//-> Save $.ajax(window.location.href, {
data: {
let saveCurrentDocument = (ev) => { markdown: mde.value()
$.ajax(window.location.href, { },
data: { dataType: 'json',
markdown: mde.value() method: 'PUT'
}, }).then((rData, rStatus, rXHR) => {
dataType: 'json', if (rData.ok) {
method: 'PUT' window.location.assign('/' + pageEntryPath)
}).then((rData, rStatus, rXHR) => { } else {
if(rData.ok) { alerts.pushError('Something went wrong', rData.error)
window.location.assign('/' + pageEntryPath); }
} else { }, (rXHR, rStatus, err) => {
alerts.pushError('Something went wrong', rData.error); alerts.pushError('Something went wrong', 'Save operation failed.')
} })
}, (rXHR, rStatus, err) => { }
alerts.pushError('Something went wrong', 'Save operation failed.');
}); $('.btn-edit-save, .btn-create-save').on('click', (ev) => {
}; saveCurrentDocument(ev)
})
$('.btn-edit-save, .btn-create-save').on('click', (ev) => {
saveCurrentDocument(ev); $(window).bind('keydown', (ev) => {
}); if (ev.ctrlKey || ev.metaKey) {
switch (String.fromCharCode(ev.which).toLowerCase()) {
$(window).bind('keydown', (ev) => { case 's':
if (ev.ctrlKey || ev.metaKey) { ev.preventDefault()
switch (String.fromCharCode(ev.which).toLowerCase()) { saveCurrentDocument(ev)
case 's': break
ev.preventDefault(); }
saveCurrentDocument(ev); }
break; })
} }
}
});
}
\ No newline at end of file
"use strict"; 'use strict'
if($('#search-input').length) { if ($('#search-input').length) {
$('#search-input').focus()
$('#search-input').focus(); $('.searchresults').css('display', 'block')
$('.searchresults').css('display', 'block'); var vueHeader = new Vue({
el: '#header-container',
var vueHeader = new Vue({ data: {
el: '#header-container', searchq: '',
data: { searchres: [],
searchq: '', searchsuggest: [],
searchres: [], searchload: 0,
searchsuggest: [], searchactive: false,
searchload: 0, searchmoveidx: 0,
searchactive: false, searchmovekey: '',
searchmoveidx: 0, searchmovearr: []
searchmovekey: '', },
searchmovearr: [] watch: {
}, searchq: (val, oldVal) => {
watch: { vueHeader.searchmoveidx = 0
searchq: (val, oldVal) => { if (val.length >= 3) {
vueHeader.searchmoveidx = 0; vueHeader.searchactive = true
if(val.length >= 3) { vueHeader.searchload++
vueHeader.searchactive = true; socket.emit('search', { terms: val }, (data) => {
vueHeader.searchload++; vueHeader.searchres = data.match
socket.emit('search', { terms: val }, (data) => { vueHeader.searchsuggest = data.suggest
vueHeader.searchres = data.match; vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
vueHeader.searchsuggest = data.suggest; if (vueHeader.searchload > 0) { vueHeader.searchload-- }
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest); })
if(vueHeader.searchload > 0) { vueHeader.searchload--; } } else {
}); vueHeader.searchactive = false
} else { vueHeader.searchres = []
vueHeader.searchactive = false; vueHeader.searchsuggest = []
vueHeader.searchres = []; vueHeader.searchmovearr = []
vueHeader.searchsuggest = []; vueHeader.searchload = 0
vueHeader.searchmovearr = []; }
vueHeader.searchload = 0; },
} searchmoveidx: (val, oldVal) => {
}, if (val > 0) {
searchmoveidx: (val, oldVal) => { vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
if(val > 0) {
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
'res.' + vueHeader.searchmovearr[val - 1]._id : 'res.' + vueHeader.searchmovearr[val - 1]._id :
'sug.' + vueHeader.searchmovearr[val - 1]; 'sug.' + vueHeader.searchmovearr[val - 1]
} else { } else {
vueHeader.searchmovekey = ''; vueHeader.searchmovekey = ''
} }
} }
}, },
methods: { methods: {
useSuggestion: (sug) => { useSuggestion: (sug) => {
vueHeader.searchq = sug; vueHeader.searchq = sug
}, },
closeSearch: () => { closeSearch: () => {
vueHeader.searchq = ''; vueHeader.searchq = ''
}, },
moveSelectSearch: () => { moveSelectSearch: () => {
if(vueHeader.searchmoveidx < 1) { return; } if (vueHeader.searchmoveidx < 1) { return }
let i = vueHeader.searchmoveidx - 1; let i = vueHeader.searchmoveidx - 1
if(vueHeader.searchmovearr[i]) {
window.location.assign('/' + vueHeader.searchmovearr[i]._id);
} else {
vueHeader.searchq = vueHeader.searchmovearr[i];
}
},
moveDownSearch: () => {
if(vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
vueHeader.searchmoveidx++;
}
},
moveUpSearch: () => {
if(vueHeader.searchmoveidx > 0) {
vueHeader.searchmoveidx--;
}
}
}
});
$('main').on('click', vueHeader.closeSearch); if (vueHeader.searchmovearr[i]) {
window.location.assign('/' + vueHeader.searchmovearr[i]._id)
} else {
vueHeader.searchq = vueHeader.searchmovearr[i]
}
},
moveDownSearch: () => {
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
vueHeader.searchmoveidx++
}
},
moveUpSearch: () => {
if (vueHeader.searchmoveidx > 0) {
vueHeader.searchmoveidx--
}
}
}
})
} $('main').on('click', vueHeader.closeSearch)
\ No newline at end of file }
function setInputSelection(input, startPos, endPos) { function setInputSelection (input, startPos, endPos) {
input.focus(); input.focus()
if (typeof input.selectionStart != "undefined") { if (typeof input.selectionStart !== 'undefined') {
input.selectionStart = startPos; input.selectionStart = startPos
input.selectionEnd = endPos; input.selectionEnd = endPos
} else if (document.selection && document.selection.createRange) { } else if (document.selection && document.selection.createRange) {
// IE branch // IE branch
input.select(); input.select()
var range = document.selection.createRange(); var range = document.selection.createRange()
range.collapse(true); range.collapse(true)
range.moveEnd("character", endPos); range.moveEnd('character', endPos)
range.moveStart("character", startPos); range.moveStart('character', startPos)
range.select(); range.select()
} }
} }
\ No newline at end of file
function makeSafePath(rawPath) { function makeSafePath (rawPath) {
let rawParts = _.split(_.trim(rawPath), '/')
let rawParts = _.split(_.trim(rawPath), '/'); rawParts = _.map(rawParts, (r) => {
rawParts = _.map(rawParts, (r) => { return _.kebabCase(_.deburr(_.trim(r)))
return _.kebabCase(_.deburr(_.trim(r))); })
});
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r); }), '/'); }
}
\ No newline at end of file
"use strict"; 'use strict'
jQuery( document ).ready(function( $ ) { jQuery(document).ready(function ($) {
$('#login-user').focus()
$('#login-user').focus(); })
});
\ No newline at end of file
...@@ -2,29 +2,27 @@ ...@@ -2,29 +2,27 @@
// Vue Create User instance // Vue Create User instance
let vueCreateUser = new Vue({ let vueCreateUser = new Vue({
el: '#modal-admin-users-create', el: '#modal-admin-users-create',
data: { data: {
email: '', email: '',
provider: 'local', provider: 'local',
password: '', password: '',
name: '' name: ''
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-admin-users-create').addClass('is-active'); $('#modal-admin-users-create').addClass('is-active')
$('#modal-admin-users-create input').first().focus(); $('#modal-admin-users-create input').first().focus()
}, },
cancel: (ev) => { cancel: (ev) => {
$('#modal-admin-users-create').removeClass('is-active'); $('#modal-admin-users-create').removeClass('is-active')
vueCreateUser.email = ''; vueCreateUser.email = ''
vueCreateUser.provider = 'local'; vueCreateUser.provider = 'local'
}, },
create: (ev) => { create: (ev) => {
vueCreateUser.cancel()
}
}
})
vueCreateUser.cancel(); $('.btn-create-prompt').on('click', vueCreateUser.open)
}
}
});
$('.btn-create-prompt').on('click', vueCreateUser.open);
\ No newline at end of file
...@@ -2,21 +2,21 @@ ...@@ -2,21 +2,21 @@
// Vue Delete User instance // Vue Delete User instance
let vueDeleteUser = new Vue({ let vueDeleteUser = new Vue({
el: '#modal-admin-users-delete', el: '#modal-admin-users-delete',
data: { data: {
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-admin-users-delete').addClass('is-active'); $('#modal-admin-users-delete').addClass('is-active')
}, },
cancel: (ev) => { cancel: (ev) => {
$('#modal-admin-users-delete').removeClass('is-active'); $('#modal-admin-users-delete').removeClass('is-active')
}, },
deleteUser: (ev) => { deleteUser: (ev) => {
vueDeleteUser.cancel(); vueDeleteUser.cancel()
} }
} }
}); })
$('.btn-deluser-prompt').on('click', vueDeleteUser.open); $('.btn-deluser-prompt').on('click', vueDeleteUser.open)
\ No newline at end of file
//-> Create New Document // -> Create New Document
let suggestedCreatePath = currentBasePath + '/new-page'; let suggestedCreatePath = currentBasePath + '/new-page'
$('.btn-create-prompt').on('click', (ev) => { $('.btn-create-prompt').on('click', (ev) => {
$('#txt-create-prompt').val(suggestedCreatePath); $('#txt-create-prompt').val(suggestedCreatePath)
$('#modal-create-prompt').toggleClass('is-active'); $('#modal-create-prompt').toggleClass('is-active')
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length); setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length)
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden'); $('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
}); })
$('#txt-create-prompt').on('keypress', (ev) => { $('#txt-create-prompt').on('keypress', (ev) => {
if(ev.which === 13) { if (ev.which === 13) {
$('.btn-create-go').trigger('click'); $('.btn-create-go').trigger('click')
} }
}); })
$('.btn-create-go').on('click', (ev) => { $('.btn-create-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-create-prompt').val())
let newDocPath = makeSafePath($('#txt-create-prompt').val()); if (_.isEmpty(newDocPath)) {
if(_.isEmpty(newDocPath)) { $('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden'); } else {
} else { $('#txt-create-prompt').parent().addClass('is-loading')
$('#txt-create-prompt').parent().addClass('is-loading'); window.location.assign('/create/' + newDocPath)
window.location.assign('/create/' + newDocPath); }
} })
});
\ No newline at end of file
//-> Move Existing Document // -> Move Existing Document
if(currentBasePath !== '') { if (currentBasePath !== '') {
$('.btn-move-prompt').removeClass('is-hidden'); $('.btn-move-prompt').removeClass('is-hidden')
} }
let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1; let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
$('.btn-move-prompt').on('click', (ev) => { $('.btn-move-prompt').on('click', (ev) => {
$('#txt-move-prompt').val(currentBasePath); $('#txt-move-prompt').val(currentBasePath)
$('#modal-move-prompt').toggleClass('is-active'); $('#modal-move-prompt').toggleClass('is-active')
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length); setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length)
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden'); $('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
}); })
$('#txt-move-prompt').on('keypress', (ev) => { $('#txt-move-prompt').on('keypress', (ev) => {
if(ev.which === 13) { if (ev.which === 13) {
$('.btn-move-go').trigger('click'); $('.btn-move-go').trigger('click')
} }
}); })
$('.btn-move-go').on('click', (ev) => { $('.btn-move-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-move-prompt').val())
let newDocPath = makeSafePath($('#txt-move-prompt').val()); if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
if(_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') { $('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden'); } else {
} else { $('#txt-move-prompt').parent().addClass('is-loading')
$('#txt-move-prompt').parent().addClass('is-loading');
$.ajax(window.location.href, {
$.ajax(window.location.href, { data: {
data: { move: newDocPath
move: newDocPath },
}, dataType: 'json',
dataType: 'json', method: 'PUT'
method: 'PUT' }).then((rData, rStatus, rXHR) => {
}).then((rData, rStatus, rXHR) => { if (rData.ok) {
if(rData.ok) { window.location.assign('/' + newDocPath)
window.location.assign('/' + newDocPath); } else {
} else { alerts.pushError('Something went wrong', rData.error)
alerts.pushError('Something went wrong', rData.error); }
} }, (rXHR, rStatus, err) => {
}, (rXHR, rStatus, err) => { alerts.pushError('Something went wrong', 'Save operation failed.')
alerts.pushError('Something went wrong', 'Save operation failed.'); })
}); }
})
}
});
\ No newline at end of file
if($('#page-type-admin-profile').length) { if ($('#page-type-admin-profile').length) {
let vueProfile = new Vue({
el: '#page-type-admin-profile',
data: {
password: '********',
passwordVerify: '********',
name: ''
},
methods: {
saveUser: (ev) => {
if (vueProfile.password !== vueProfile.passwordVerify) {
alerts.pushError('Error', "Passwords don't match!")
return
}
$.post(window.location.href, {
password: vueProfile.password,
name: vueProfile.name
}).done((resp) => {
alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp)
})
}
},
created: function () {
this.name = usrDataName
}
})
} else if ($('#page-type-admin-users').length) {
let vueProfile = new Vue({ // =include ../modals/admin-users-create.js
el: '#page-type-admin-profile',
data: {
password: '********',
passwordVerify: '********',
name: ''
},
methods: {
saveUser: (ev) => {
if(vueProfile.password !== vueProfile.passwordVerify) {
alerts.pushError('Error', "Passwords don't match!");
return;
}
$.post(window.location.href, {
password: vueProfile.password,
name: vueProfile.name
}).done((resp) => {
alerts.pushSuccess('Saved successfully', 'Changes have been applied.');
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp);
})
}
},
created: function() {
this.name = usrDataName;
}
});
} else if($('#page-type-admin-users').length) { } else if ($('#page-type-admin-users-edit').length) {
let vueEditUser = new Vue({
el: '#page-type-admin-users-edit',
data: {
id: '',
email: '',
password: '********',
name: '',
rights: [],
roleoverride: 'none'
},
methods: {
addRightsRow: (ev) => {
vueEditUser.rights.push({
role: 'write',
path: '/',
exact: false,
deny: false
})
},
removeRightsRow: (idx) => {
_.pullAt(vueEditUser.rights, idx)
vueEditUser.$forceUpdate()
},
saveUser: (ev) => {
let formattedRights = _.cloneDeep(vueEditUser.rights)
switch (vueEditUser.roleoverride) {
case 'admin':
formattedRights.push({
role: 'admin',
path: '/',
exact: false,
deny: false
})
break
}
$.post(window.location.href, {
password: vueEditUser.password,
name: vueEditUser.name,
rights: JSON.stringify(formattedRights)
}).done((resp) => {
alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp)
})
}
},
created: function () {
this.id = usrData._id
this.email = usrData.email
this.name = usrData.name
//=include ../modals/admin-users-create.js if (_.find(usrData.rights, { role: 'admin' })) {
this.rights = _.reject(usrData.rights, ['role', 'admin'])
this.roleoverride = 'admin'
} else {
this.rights = usrData.rights
}
}
})
} else if($('#page-type-admin-users-edit').length) { // =include ../modals/admin-users-delete.js
}
let vueEditUser = new Vue({
el: '#page-type-admin-users-edit',
data: {
id: '',
email: '',
password: '********',
name: '',
rights: [],
roleoverride: 'none'
},
methods: {
addRightsRow: (ev) => {
vueEditUser.rights.push({
role: 'write',
path: '/',
exact: false,
deny: false
});
},
removeRightsRow: (idx) => {
_.pullAt(vueEditUser.rights, idx)
vueEditUser.$forceUpdate()
},
saveUser: (ev) => {
let formattedRights = _.cloneDeep(vueEditUser.rights)
switch(vueEditUser.roleoverride) {
case 'admin':
formattedRights.push({
role: 'admin',
path: '/',
exact: false,
deny: false
})
break;
}
$.post(window.location.href, {
password: vueEditUser.password,
name: vueEditUser.name,
rights: JSON.stringify(formattedRights)
}).done((resp) => {
alerts.pushSuccess('Saved successfully', 'Changes have been applied.');
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp);
})
}
},
created: function() {
this.id = usrData._id;
this.email = usrData.email;
this.name = usrData.name;
if(_.find(usrData.rights, { role: 'admin' })) {
this.rights = _.reject(usrData.rights, ['role', 'admin']);
this.roleoverride = 'admin';
} else {
this.rights = usrData.rights;
}
}
});
//=include ../modals/admin-users-delete.js
}
\ No newline at end of file
if($('#page-type-create').length) { if ($('#page-type-create').length) {
let pageEntryPath = $('#page-type-create').data('entrypath')
let pageEntryPath = $('#page-type-create').data('entrypath'); // -> Discard
//-> Discard $('.btn-create-discard').on('click', (ev) => {
$('#modal-create-discard').toggleClass('is-active')
})
$('.btn-create-discard').on('click', (ev) => { // =include ../components/editor.js
$('#modal-create-discard').toggleClass('is-active'); }
});
//=include ../components/editor.js
}
\ No newline at end of file
if($('#page-type-edit').length) { if ($('#page-type-edit').length) {
let pageEntryPath = $('#page-type-edit').data('entrypath')
let pageEntryPath = $('#page-type-edit').data('entrypath'); // -> Discard
//-> Discard $('.btn-edit-discard').on('click', (ev) => {
$('#modal-edit-discard').toggleClass('is-active')
})
$('.btn-edit-discard').on('click', (ev) => { // =include ../components/editor.js
$('#modal-edit-discard').toggleClass('is-active'); }
});
//=include ../components/editor.js
}
\ No newline at end of file
if($('#page-type-source').length) { if ($('#page-type-source').length) {
var scEditor = ace.edit('source-display')
scEditor.setTheme('ace/theme/tomorrow_night')
scEditor.getSession().setMode('ace/mode/markdown')
scEditor.setOption('fontSize', '14px')
scEditor.setOption('hScrollBarAlwaysVisible', false)
scEditor.setOption('wrap', true)
scEditor.setReadOnly(true)
scEditor.renderer.updateFull()
var scEditor = ace.edit("source-display"); let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''
scEditor.setTheme("ace/theme/tomorrow_night");
scEditor.getSession().setMode("ace/mode/markdown");
scEditor.setOption('fontSize', '14px');
scEditor.setOption('hScrollBarAlwaysVisible', false);
scEditor.setOption('wrap', true);
scEditor.setReadOnly(true);
scEditor.renderer.updateFull();
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''; // =include ../modals/create.js
// =include ../modals/move.js
//=include ../modals/create.js }
//=include ../modals/move.js
}
\ No newline at end of file
if($('#page-type-view').length) { if ($('#page-type-view').length) {
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''; // =include ../modals/create.js
// =include ../modals/move.js
//=include ../modals/create.js }
//=include ../modals/move.js
}
\ No newline at end of file
"use strict"; 'use strict'
var express = require('express'); var express = require('express')
var router = express.Router(); var router = express.Router()
const Promise = require('bluebird'); const Promise = require('bluebird')
const validator = require('validator'); const validator = require('validator')
const _ = require('lodash'); const _ = require('lodash')
/** /**
* Admin * Admin
*/ */
router.get('/', (req, res) => { router.get('/', (req, res) => {
res.redirect('/admin/profile'); res.redirect('/admin/profile')
}); })
router.get('/profile', (req, res) => { router.get('/profile', (req, res) => {
if (res.locals.isGuest) {
return res.render('error-forbidden')
}
if(res.locals.isGuest) { res.render('pages/admin/profile', { adminTab: 'profile' })
return res.render('error-forbidden'); })
}
res.render('pages/admin/profile', { adminTab: 'profile' });
});
router.post('/profile', (req, res) => { router.post('/profile', (req, res) => {
if (res.locals.isGuest) {
if(res.locals.isGuest) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
return db.User.findById(req.user.id).then((usr) => {
return db.User.findById(req.user.id).then((usr) => { usr.name = _.trim(req.body.name)
usr.name = _.trim(req.body.name); if (usr.provider === 'local' && req.body.password !== '********') {
if(usr.provider === 'local' && req.body.password !== '********') { let nPwd = _.trim(req.body.password)
let nPwd = _.trim(req.body.password); if (nPwd.length < 6) {
if(nPwd.length < 6) { return Promise.reject(new Error('New Password too short!'))
return Promise.reject(new Error('New Password too short!')) } else {
} else { return db.User.hashPassword(nPwd).then((pwd) => {
return db.User.hashPassword(nPwd).then((pwd) => { usr.password = pwd
usr.password = pwd; return usr.save()
return usr.save(); })
}); }
} } else {
} else { return usr.save()
return usr.save(); }
} }).then(() => {
}).then(() => { return res.json({ msg: 'OK' })
return res.json({ msg: 'OK' }); }).catch((err) => {
}).catch((err) => { res.status(400).json({ msg: err.message })
res.status(400).json({ msg: err.message }); })
}) })
});
router.get('/stats', (req, res) => { router.get('/stats', (req, res) => {
if (res.locals.isGuest) {
if(res.locals.isGuest) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
Promise.all([
Promise.all([ db.Entry.count(),
db.Entry.count(), db.UplFile.count(),
db.UplFile.count(), db.User.count()
db.User.count() ]).spread((totalEntries, totalUploads, totalUsers) => {
]).spread((totalEntries, totalUploads, totalUsers) => { return res.render('pages/admin/stats', {
return res.render('pages/admin/stats', { totalEntries, totalUploads, totalUsers, adminTab: 'stats'
totalEntries, totalUploads, totalUsers, }) || true
adminTab: 'stats' }).catch((err) => {
}) || true; throw err
}).catch((err) => { })
throw err; })
});
});
router.get('/users', (req, res) => { router.get('/users', (req, res) => {
if (!res.locals.rights.manage) {
if(!res.locals.rights.manage) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
db.User.find({})
db.User.find({}) .select('-password -rights')
.select('-password -rights') .sort('name email')
.sort('name email') .exec().then((usrs) => {
.exec().then((usrs) => { res.render('pages/admin/users', { adminTab: 'users', usrs })
res.render('pages/admin/users', { adminTab: 'users', usrs }); })
}); })
});
router.get('/users/:id', (req, res) => { router.get('/users/:id', (req, res) => {
if (!res.locals.rights.manage) {
if(!res.locals.rights.manage) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
if (!validator.isMongoId(req.params.id)) {
if(!validator.isMongoId(req.params.id)) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
db.User.findById(req.params.id)
db.User.findById(req.params.id) .select('-password -providerId')
.select('-password -providerId') .exec().then((usr) => {
.exec().then((usr) => { let usrOpts = {
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
let usrOpts = { canChangeName: (usr.email !== 'guest'),
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin), canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
canChangeName: (usr.email !== 'guest'), canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'), canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)), }
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
}; res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts })
})
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts }); })
});
});
router.post('/users/:id', (req, res) => { router.post('/users/:id', (req, res) => {
if (!res.locals.rights.manage) {
if(!res.locals.rights.manage) { return res.status(401).json({ msg: 'Unauthorized' })
return res.status(401).json({ msg: 'Unauthorized' }); }
}
if (!validator.isMongoId(req.params.id)) {
if(!validator.isMongoId(req.params.id)) { return res.status(400).json({ msg: 'Invalid User ID' })
return res.status(400).json({ msg: 'Invalid User ID' }); }
}
return db.User.findById(req.params.id).then((usr) => {
return db.User.findById(req.params.id).then((usr) => { usr.name = _.trim(req.body.name)
usr.name = _.trim(req.body.name); usr.rights = JSON.parse(req.body.rights)
usr.rights = JSON.parse(req.body.rights); if (usr.provider === 'local' && req.body.password !== '********') {
if(usr.provider === 'local' && req.body.password !== '********') { let nPwd = _.trim(req.body.password)
let nPwd = _.trim(req.body.password); if (nPwd.length < 6) {
if(nPwd.length < 6) { return Promise.reject(new Error('New Password too short!'))
return Promise.reject(new Error('New Password too short!')) } else {
} else { return db.User.hashPassword(nPwd).then((pwd) => {
return db.User.hashPassword(nPwd).then((pwd) => { usr.password = pwd
usr.password = pwd; return usr.save()
return usr.save(); })
}); }
} } else {
} else { return usr.save()
return usr.save(); }
} }).then(() => {
}).then(() => { return res.json({ msg: 'OK' })
return res.json({ msg: 'OK' }); }).catch((err) => {
}).catch((err) => { res.status(400).json({ msg: err.message })
res.status(400).json({ msg: err.message }); })
}) })
});
router.get('/settings', (req, res) => { router.get('/settings', (req, res) => {
if (!res.locals.rights.manage) {
return res.render('error-forbidden')
}
if(!res.locals.rights.manage) { res.render('pages/admin/settings', { adminTab: 'settings' })
return res.render('error-forbidden'); })
}
res.render('pages/admin/settings', { adminTab: 'settings' });
});
module.exports = router; module.exports = router
\ No newline at end of file
var express = require('express'); 'use strict'
var router = express.Router();
var passport = require('passport'); const express = require('express')
var ExpressBrute = require('express-brute'); const router = express.Router()
var ExpressBruteMongooseStore = require('express-brute-mongoose'); const passport = require('passport')
var moment = require('moment'); const ExpressBrute = require('express-brute')
const ExpressBruteMongooseStore = require('express-brute-mongoose')
const moment = require('moment')
/** /**
* Setup Express-Brute * Setup Express-Brute
*/ */
var EBstore = new ExpressBruteMongooseStore(db.Bruteforce); const EBstore = new ExpressBruteMongooseStore(db.Bruteforce)
var bruteforce = new ExpressBrute(EBstore, { const bruteforce = new ExpressBrute(EBstore, {
freeRetries: 5, freeRetries: 5,
minWait: 60 * 1000, minWait: 60 * 1000,
maxWait: 5 * 60 * 1000, maxWait: 5 * 60 * 1000,
refreshTimeoutOnRequest: false, refreshTimeoutOnRequest: false,
failCallback(req, res, next, nextValidRequestDate) { failCallback (req, res, next, nextValidRequestDate) {
req.flash('alert', { req.flash('alert', {
class: 'error', class: 'error',
title: 'Too many attempts!', title: 'Too many attempts!',
message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.', message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.',
iconClass: 'fa-times' iconClass: 'fa-times'
}); })
res.redirect('/login'); res.redirect('/login')
} }
}); })
/** /**
* Login form * Login form
*/ */
router.get('/login', function(req, res, next) { router.get('/login', function (req, res, next) {
res.render('auth/login', { res.render('auth/login', {
usr: res.locals.usr usr: res.locals.usr
}); })
}); })
router.post('/login', bruteforce.prevent, function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); } router.post('/login', bruteforce.prevent, function (req, res, next) {
passport.authenticate('local', function (err, user, info) {
if (err) { return next(err) }
if (!user) { if (!user) {
req.flash('alert', { req.flash('alert', {
title: 'Invalid login', title: 'Invalid login',
message: "The email or password is invalid." message: 'The email or password is invalid.'
}); })
return res.redirect('/login'); return res.redirect('/login')
} }
req.logIn(user, function(err) { req.logIn(user, function (err) {
if (err) { return next(err); } if (err) { return next(err) }
req.brute.reset(function () { req.brute.reset(function () {
return res.redirect('/'); return res.redirect('/')
}); })
}); })
})(req, res, next)
})(req, res, next); })
});
/** /**
* Social Login * Social Login
*/ */
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] })); router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] })); router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }))
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] })); router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
/** /**
* Logout * Logout
*/ */
router.get('/logout', function(req, res) { router.get('/logout', function (req, res) {
req.logout(); req.logout()
res.redirect('/'); res.redirect('/')
}); })
module.exports = router; module.exports = router
\ No newline at end of file
"use strict"; 'use strict'
var express = require('express'); const express = require('express')
var router = express.Router(); const router = express.Router()
var _ = require('lodash'); const _ = require('lodash')
// ========================================== // ==========================================
// EDIT MODE // EDIT MODE
...@@ -12,132 +12,123 @@ var _ = require('lodash'); ...@@ -12,132 +12,123 @@ var _ = require('lodash');
* Edit document in Markdown * Edit document in Markdown
*/ */
router.get('/edit/*', (req, res, next) => { router.get('/edit/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
entries.fetchOriginal(safePath, {
entries.fetchOriginal(safePath, { parseMarkdown: false,
parseMarkdown: false, parseMeta: true,
parseMeta: true, parseTree: false,
parseTree: false, includeMarkdown: true,
includeMarkdown: true, includeParentInfo: false,
includeParentInfo: false, cache: false
cache: false }).then((pageData) => {
}).then((pageData) => { if (pageData) {
if(pageData) { res.render('pages/edit', { pageData })
res.render('pages/edit', { pageData }); } else {
} else { throw new Error('Invalid page path.')
throw new Error('Invalid page path.'); }
} return true
return true; }).catch((err) => {
}).catch((err) => { res.render('error', {
res.render('error', { message: err.message,
message: err.message, error: {}
error: {} })
}); })
}); })
});
router.put('/edit/*', (req, res, next) => { router.put('/edit/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Forbidden'
error: 'Forbidden' })
}); }
}
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
entries.update(safePath, req.body.markdown).then(() => {
entries.update(safePath, req.body.markdown).then(() => { return res.json({
return res.json({ ok: true
ok: true }) || true
}) || true; }).catch((err) => {
}).catch((err) => { res.json({
res.json({ ok: false,
ok: false, error: err.message
error: err.message })
}); })
}); })
});
// ========================================== // ==========================================
// CREATE MODE // CREATE MODE
// ========================================== // ==========================================
router.get('/create/*', (req, res, next) => { router.get('/create/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
if (_.some(['create', 'edit', 'account', 'source', 'history', 'mk'], (e) => { return _.startsWith(req.path, '/create/' + e) })) {
if(_.some(['create','edit','account','source','history','mk'], (e) => { return _.startsWith(req.path, '/create/' + e); })) { return res.render('error', {
return res.render('error', { message: 'You cannot create a document with this name as it is reserved by the system.',
message: 'You cannot create a document with this name as it is reserved by the system.', error: {}
error: {} })
}); }
}
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
entries.exists(safePath).then((docExists) => {
entries.exists(safePath).then((docExists) => { if (!docExists) {
if(!docExists) { return entries.getStarter(safePath).then((contents) => {
return entries.getStarter(safePath).then((contents) => { let pageData = {
markdown: contents,
let pageData = { meta: {
markdown: contents, title: _.startCase(safePath),
meta: { path: safePath
title: _.startCase(safePath), }
path: safePath }
} res.render('pages/create', { pageData })
};
res.render('pages/create', { pageData }); return true
}).catch((err) => {
return true; winston.warn(err)
throw new Error('Could not load starter content!')
}).catch((err) => { })
throw new Error('Could not load starter content!'); } else {
}); throw new Error('This entry already exists!')
} else { }
throw new Error('This entry already exists!'); }).catch((err) => {
} res.render('error', {
}).catch((err) => { message: err.message,
res.render('error', { error: {}
message: err.message, })
error: {} })
}); })
});
});
router.put('/create/*', (req, res, next) => { router.put('/create/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Forbidden'
error: 'Forbidden' })
}); }
}
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
entries.create(safePath, req.body.markdown).then(() => {
entries.create(safePath, req.body.markdown).then(() => { return res.json({
return res.json({ ok: true
ok: true }) || true
}) || true; }).catch((err) => {
}).catch((err) => { return res.json({
return res.json({ ok: false,
ok: false, error: err.message
error: err.message })
}); })
}); })
});
// ========================================== // ==========================================
// VIEW MODE // VIEW MODE
...@@ -147,102 +138,94 @@ router.put('/create/*', (req, res, next) => { ...@@ -147,102 +138,94 @@ router.put('/create/*', (req, res, next) => {
* View source of a document * View source of a document
*/ */
router.get('/source/*', (req, res, next) => { router.get('/source/*', (req, res, next) => {
let safePath = entries.parsePath(_.replace(req.path, '/source', ''))
let safePath = entries.parsePath(_.replace(req.path, '/source', ''));
entries.fetchOriginal(safePath, {
entries.fetchOriginal(safePath, { parseMarkdown: false,
parseMarkdown: false, parseMeta: true,
parseMeta: true, parseTree: false,
parseTree: false, includeMarkdown: true,
includeMarkdown: true, includeParentInfo: false,
includeParentInfo: false, cache: false
cache: false }).then((pageData) => {
}).then((pageData) => { if (pageData) {
if(pageData) { res.render('pages/source', { pageData })
res.render('pages/source', { pageData }); } else {
} else { throw new Error('Invalid page path.')
throw new Error('Invalid page path.'); }
} return true
return true; }).catch((err) => {
}).catch((err) => { res.render('error', {
res.render('error', { message: err.message,
message: err.message, error: {}
error: {} })
}); })
}); })
});
/** /**
* View document * View document
*/ */
router.get('/*', (req, res, next) => { router.get('/*', (req, res, next) => {
let safePath = entries.parsePath(req.path)
let safePath = entries.parsePath(req.path);
entries.fetch(safePath).then((pageData) => {
entries.fetch(safePath).then((pageData) => { if (pageData) {
if(pageData) { res.render('pages/view', { pageData })
res.render('pages/view', { pageData }); } else {
} else { res.render('error-notexist', {
res.render('error-notexist', { newpath: safePath
newpath: safePath })
}); }
} return true
return true; }).error((err) => {
}).error((err) => { if (safePath === 'home') {
res.render('pages/welcome')
if(safePath === 'home') { } else {
res.render('pages/welcome'); res.render('error-notexist', {
} else { message: err.message,
res.render('error-notexist', { newpath: safePath
message: err.message, })
newpath: safePath }
}); }).catch((err) => {
} res.render('error', {
message: err.message,
}).catch((err) => { error: {}
res.render('error', { })
message: err.message, })
error: {} })
});
});
});
/** /**
* Move document * Move document
*/ */
router.put('/*', (req, res, next) => { router.put('/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Forbidden'
error: 'Forbidden' })
}); }
}
let safePath = entries.parsePath(req.path)
let safePath = entries.parsePath(req.path);
if (_.isEmpty(req.body.move)) {
if(_.isEmpty(req.body.move)) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Invalid document action call.'
error: 'Invalid document action call.' })
}); }
}
let safeNewPath = entries.parsePath(req.body.move)
let safeNewPath = entries.parsePath(req.body.move);
entries.move(safePath, safeNewPath).then(() => {
entries.move(safePath, safeNewPath).then(() => { res.json({
res.json({ ok: true
ok: true })
}); }).catch((err) => {
}).catch((err) => { res.json({
res.json({ ok: false,
ok: false, error: err.message
error: err.message })
}); })
}); })
}); module.exports = router
module.exports = router;
\ No newline at end of file
"use strict"; 'use strict'
var express = require('express'); const express = require('express')
var router = express.Router(); const router = express.Router()
var readChunk = require('read-chunk'), const readChunk = require('read-chunk')
fileType = require('file-type'), const fileType = require('file-type')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
path = require('path'), const path = require('path')
_ = require('lodash'); const _ = require('lodash')
var validPathRe = new RegExp("^([a-z0-9\\/-]+\\.[a-z0-9]+)$"); const validPathRe = new RegExp('^([a-z0-9\\/-]+\\.[a-z0-9]+)$')
var validPathThumbsRe = new RegExp("^([0-9]+\\.png)$"); const validPathThumbsRe = new RegExp('^([0-9]+\\.png)$')
// ========================================== // ==========================================
// SERVE UPLOADS FILES // SERVE UPLOADS FILES
// ========================================== // ==========================================
router.get('/t/*', (req, res, next) => { router.get('/t/*', (req, res, next) => {
let fileName = req.params[0]
let fileName = req.params[0]; if (!validPathThumbsRe.test(fileName)) {
if(!validPathThumbsRe.test(fileName)) { return res.sendStatus(404).end()
return res.sendStatus(404).end(); }
}
// todo: Authentication-based access
//todo: Authentication-based access
res.sendFile(fileName, {
res.sendFile(fileName, { root: lcdata.getThumbsPath(),
root: lcdata.getThumbsPath(), dotfiles: 'deny'
dotfiles: 'deny' }, (err) => {
}, (err) => { if (err) {
if (err) { res.status(err.status).end()
res.status(err.status).end(); }
} })
}); })
});
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => { router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
let destFolder = _.chain(req.body.folder).trim().toLower().value();
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { if (!destFolderPath) {
res.json({ ok: false, msg: 'Invalid Folder' })
if(!destFolderPath) { return true
res.json({ ok: false, msg: 'Invalid Folder' }); }
return true;
} Promise.map(req.files, (f) => {
let destFilename = ''
Promise.map(req.files, (f) => { let destFilePath = ''
let destFilename = ''; return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
let destFilePath = ''; destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename)
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
return readChunk(f.path, 0, 262)
destFilename = fname; }).then((buf) => {
destFilePath = path.resolve(destFolderPath, destFilename); // -> Check MIME type by magic number
return readChunk(f.path, 0, 262); let mimeInfo = fileType(buf)
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
}).then((buf) => { return Promise.reject(new Error('Invalid file type.'))
}
//-> Check MIME type by magic number return true
}).then(() => {
let mimeInfo = fileType(buf); // -> Move file to final destination
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
return Promise.reject(new Error('Invalid file type.')); return fs.moveAsync(f.path, destFilePath, { clobber: false })
} }).then(() => {
return true; return {
ok: true,
}).then(() => { filename: destFilename,
filesize: f.size
//-> Move file to final destination }
}).reflect()
return fs.moveAsync(f.path, destFilePath, { clobber: false }); }, {concurrency: 3}).then((results) => {
let uplResults = _.map(results, (r) => {
}).then(() => { if (r.isFulfilled()) {
return { return r.value()
ok: true, } else {
filename: destFilename, return {
filesize: f.size ok: false,
}; msg: r.reason().message
}).reflect(); }
}
}, {concurrency: 3}).then((results) => { })
let uplResults = _.map(results, (r) => { res.json({ ok: true, results: uplResults })
if(r.isFulfilled()) { return true
return r.value(); }).catch((err) => {
} else { res.json({ ok: false, msg: err.message })
return { return true
ok: false, })
msg: r.reason().message })
}; })
}
});
res.json({ ok: true, results: uplResults });
return true;
}).catch((err) => {
res.json({ ok: false, msg: err.message });
return true;
});
});
});
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => { router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
let destFolder = _.chain(req.body.folder).trim().toLower().value();
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { if (!destFolderPath) {
res.json({ ok: false, msg: 'Invalid Folder' })
if(!destFolderPath) { return true
res.json({ ok: false, msg: 'Invalid Folder' }); }
return true;
} Promise.map(req.files, (f) => {
let destFilename = ''
Promise.map(req.files, (f) => { let destFilePath = ''
let destFilename = ''; return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
let destFilePath = ''; destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename)
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
// -> Move file to final destination
destFilename = fname;
destFilePath = path.resolve(destFolderPath, destFilename); return fs.moveAsync(f.path, destFilePath, { clobber: false })
}).then(() => {
//-> Move file to final destination return {
ok: true,
return fs.moveAsync(f.path, destFilePath, { clobber: false }); filename: destFilename,
filesize: f.size
}).then(() => { }
return { }).reflect()
ok: true, }, {concurrency: 3}).then((results) => {
filename: destFilename, let uplResults = _.map(results, (r) => {
filesize: f.size if (r.isFulfilled()) {
}; return r.value()
}).reflect(); } else {
return {
}, {concurrency: 3}).then((results) => { ok: false,
let uplResults = _.map(results, (r) => { msg: r.reason().message
if(r.isFulfilled()) { }
return r.value(); }
} else { })
return { res.json({ ok: true, results: uplResults })
ok: false, return true
msg: r.reason().message }).catch((err) => {
}; res.json({ ok: false, msg: err.message })
} return true
}); })
res.json({ ok: true, results: uplResults }); })
return true; })
}).catch((err) => {
res.json({ ok: false, msg: err.message });
return true;
});
});
});
router.get('/*', (req, res, next) => { router.get('/*', (req, res, next) => {
let fileName = req.params[0]
let fileName = req.params[0]; if (!validPathRe.test(fileName)) {
if(!validPathRe.test(fileName)) { return res.sendStatus(404).end()
return res.sendStatus(404).end(); }
}
// todo: Authentication-based access
//todo: Authentication-based access
res.sendFile(fileName, {
res.sendFile(fileName, { root: git.getRepoPath() + '/uploads/',
root: git.getRepoPath() + '/uploads/', dotfiles: 'deny'
dotfiles: 'deny' }, (err) => {
}, (err) => { if (err) {
if (err) { res.status(err.status).end()
res.status(err.status).end(); }
} })
}); })
}); module.exports = router
module.exports = router;
\ No newline at end of file
"use strict"; 'use strict'
const _ = require('lodash')
module.exports = (socket) => { module.exports = (socket) => {
if (!socket.request.user.logged_in) {
if(!socket.request.user.logged_in) { return
return;
} }
//----------------------------------------- // -----------------------------------------
// SEARCH // SEARCH
//----------------------------------------- // -----------------------------------------
socket.on('search', (data, cb) => { socket.on('search', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
entries.search(data.terms).then((results) => { entries.search(data.terms).then((results) => {
return cb(results) || true; return cb(results) || true
}); })
}); })
//----------------------------------------- // -----------------------------------------
// UPLOADS // UPLOADS
//----------------------------------------- // -----------------------------------------
socket.on('uploadsGetFolders', (data, cb) => { socket.on('uploadsGetFolders', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFolders().then((f) => { upl.getUploadsFolders().then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsCreateFolder', (data, cb) => { socket.on('uploadsCreateFolder', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.createUploadsFolder(data.foldername).then((f) => { upl.createUploadsFolder(data.foldername).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsGetImages', (data, cb) => { socket.on('uploadsGetImages', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFiles('image', data.folder).then((f) => { upl.getUploadsFiles('image', data.folder).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsGetFiles', (data, cb) => { socket.on('uploadsGetFiles', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFiles('binary', data.folder).then((f) => { upl.getUploadsFiles('binary', data.folder).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsDeleteFile', (data, cb) => { socket.on('uploadsDeleteFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.deleteUploadsFile(data.uid).then((f) => { upl.deleteUploadsFile(data.uid).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsFetchFileFromURL', (data, cb) => { socket.on('uploadsFetchFileFromURL', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => { upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
socket.on('uploadsRenameFile', (data, cb) => { socket.on('uploadsRenameFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => { upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
socket.on('uploadsMoveFile', (data, cb) => { socket.on('uploadsMoveFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.moveUploadsFile(data.uid, data.folder).then((f) => { upl.moveUploadsFile(data.uid, data.folder).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
}
};
\ No newline at end of file
"use strict"; 'use strict'
const crypto = require('crypto'); const crypto = require('crypto')
/** /**
* Internal Authentication * Internal Authentication
*/ */
module.exports = { module.exports = {
_curKey: false, _curKey: false,
init(inKey) { init (inKey) {
this._curKey = inKey
this._curKey = inKey; return this
},
return this; generateKey () {
return crypto.randomBytes(20).toString('hex')
},
}, validateKey (inKey) {
return inKey === this._curKey
}
generateKey() { }
return crypto.randomBytes(20).toString('hex');
},
validateKey(inKey) {
return inKey === this._curKey;
}
};
\ No newline at end of file
"use strict"; 'use strict'
var path = require('path'), const path = require('path')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
multer = require('multer'), const multer = require('multer')
os = require('os'), const os = require('os')
_ = require('lodash'); const _ = require('lodash')
/** /**
* Local Data Storage * Local Data Storage
*/ */
module.exports = { module.exports = {
_uploadsPath: './repo/uploads', _uploadsPath: './repo/uploads',
_uploadsThumbsPath: './data/thumbs', _uploadsThumbsPath: './data/thumbs',
uploadImgHandler: null, uploadImgHandler: null,
/** /**
* Initialize Local Data Storage model * Initialize Local Data Storage model
* *
* @return {Object} Local Data Storage model instance * @return {Object} Local Data Storage model instance
*/ */
init() { init () {
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads'); this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
this.createBaseDirectories(appconfig)
this.createBaseDirectories(appconfig); this.initMulter(appconfig)
this.initMulter(appconfig);
return this
return this; },
}, /**
* Init Multer upload handlers
/** *
* Init Multer upload handlers * @param {Object} appconfig The application config
* * @return {boolean} Void
* @param {Object} appconfig The application config */
* @return {boolean} Void initMulter (appconfig) {
*/ let maxFileSizes = {
initMulter(appconfig) { img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
let maxFileSizes = { }
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024 // -> IMAGES
};
this.uploadImgHandler = multer({
//-> IMAGES storage: multer.diskStorage({
destination: (req, f, cb) => {
this.uploadImgHandler = multer({ cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
storage: multer.diskStorage({ }
destination: (req, f, cb) => { }),
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')); fileFilter: (req, f, cb) => {
} // -> Check filesize
}),
fileFilter: (req, f, cb) => { if (f.size > maxFileSizes.img) {
return cb(null, false)
//-> Check filesize }
if(f.size > maxFileSizes.img) { // -> Check MIME type (quick check only)
return cb(null, false);
} if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
return cb(null, false)
//-> Check MIME type (quick check only) }
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) { cb(null, true)
return cb(null, false); }
} }).array('imgfile', 20)
cb(null, true); // -> FILES
}
}).array('imgfile', 20); this.uploadFileHandler = multer({
storage: multer.diskStorage({
//-> FILES destination: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
this.uploadFileHandler = multer({ }
storage: multer.diskStorage({ }),
destination: (req, f, cb) => { fileFilter: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')); // -> Check filesize
}
}), if (f.size > maxFileSizes.file) {
fileFilter: (req, f, cb) => { return cb(null, false)
}
//-> Check filesize
cb(null, true)
if(f.size > maxFileSizes.file) { }
return cb(null, false); }).array('binfile', 20)
}
return true
cb(null, true); },
}
}).array('binfile', 20); /**
* Creates a base directories (Synchronous).
return true; *
* @param {Object} appconfig The application config
}, * @return {Void} Void
*/
/** createBaseDirectories (appconfig) {
* Creates a base directories (Synchronous). winston.info('[SERVER] Checking data directories...')
*
* @param {Object} appconfig The application config try {
* @return {Void} Void fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
*/ fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'))
createBaseDirectories(appconfig) { fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'))
winston.info('[SERVER] Checking data directories...');
if (os.type() !== 'Windows_NT') {
try { fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644')
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data)); }
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'));
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs')); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload')); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'))
if(os.type() !== 'Windows_NT') { if (os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644'); fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644')
} }
} catch (err) {
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo)); winston.error(err)
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads')); }
if(os.type() !== 'Windows_NT') { winston.info('[SERVER] Data and Repository directories are OK.')
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644');
} return
},
} catch (err) {
winston.error(err); /**
} * Gets the uploads path.
*
winston.info('[SERVER] Data and Repository directories are OK.'); * @return {String} The uploads path.
*/
return; getUploadsPath () {
return this._uploadsPath
}, },
/** /**
* Gets the uploads path. * Gets the thumbnails folder path.
* *
* @return {String} The uploads path. * @return {String} The thumbs path.
*/ */
getUploadsPath() { getThumbsPath () {
return this._uploadsPath; return this._uploadsThumbsPath
}, },
/** /**
* Gets the thumbnails folder path. * Check if filename is valid and unique
* *
* @return {String} The thumbs path. * @param {String} f The filename
*/ * @param {String} fld The containing folder
getThumbsPath() { * @param {boolean} isImage Indicates if image
return this._uploadsThumbsPath; * @return {Promise<String>} Promise of the accepted filename
}, */
validateUploadsFilename (f, fld, isImage) {
/** let fObj = path.parse(f)
* Check if filename is valid and unique let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9-]+/g, '')
* let fext = _.toLower(fObj.ext)
* @param {String} f The filename
* @param {String} fld The containing folder if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
* @param {boolean} isImage Indicates if image fext = '.png'
* @return {Promise<String>} Promise of the accepted filename }
*/
validateUploadsFilename(f, fld, isImage) { f = fname + fext
let fpath = path.resolve(this._uploadsPath, fld, f)
let fObj = path.parse(f);
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, ''); return fs.statAsync(fpath).then((s) => {
let fext = _.toLower(fObj.ext); throw new Error('File ' + f + ' already exists.')
}).catch((err) => {
if(isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) { if (err.code === 'ENOENT') {
fext = '.png'; return f
} }
throw err
f = fname + fext; })
let fpath = path.resolve(this._uploadsPath, fld, f); }
return fs.statAsync(fpath).then((s) => { }
throw new Error('File ' + f + ' already exists.');
}).catch((err) => {
if(err.code === 'ENOENT') {
return f;
}
throw err;
});
},
};
\ No newline at end of file
"use strict"; 'use strict'
var Promise = require('bluebird'), const moment = require('moment-timezone')
moment = require('moment-timezone');
/** /**
* Authentication middleware * Authentication middleware
...@@ -12,29 +11,27 @@ var Promise = require('bluebird'), ...@@ -12,29 +11,27 @@ var Promise = require('bluebird'),
* @return {any} void * @return {any} void
*/ */
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
// Is user authenticated ?
// Is user authenticated ? if (!req.isAuthenticated()) {
return res.redirect('/login')
}
if (!req.isAuthenticated()) { // Check permissions
return res.redirect('/login');
}
// Check permissions if (!rights.check(req, 'read')) {
return res.render('error-forbidden')
}
if(!rights.check(req, 'read')) { // Set i18n locale
return res.render('error-forbidden');
}
// Set i18n locale req.i18n.changeLanguage(req.user.lang)
res.locals.userMoment = moment
res.locals.userMoment.locale(req.user.lang)
req.i18n.changeLanguage(req.user.lang); // Expose user data
res.locals.userMoment = moment;
res.locals.userMoment.locale(req.user.lang);
// Expose user data res.locals.user = req.user
res.locals.user = req.user; return next()
}
return next();
};
\ No newline at end of file
"use strict"; 'use strict'
/** /**
* Flash middleware * Flash middleware
...@@ -9,9 +9,7 @@ ...@@ -9,9 +9,7 @@
* @return {any} void * @return {any} void
*/ */
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
res.locals.appflash = req.flash('alert')
res.locals.appflash = req.flash('alert'); next()
}
next();
};
\ No newline at end of file
'use strict'
/** /**
* Security Middleware * Security Middleware
* *
...@@ -6,23 +8,21 @@ ...@@ -6,23 +8,21 @@
* @param {Function} next next callback function * @param {Function} next next callback function
* @return {any} void * @return {any} void
*/ */
module.exports = function(req, res, next) { module.exports = function (req, res, next) {
// -> Disable X-Powered-By
//-> Disable X-Powered-By app.disable('x-powered-by')
app.disable('x-powered-by');
//-> Disable Frame Embedding
res.set('X-Frame-Options', 'deny');
//-> Re-enable XSS Fitler if disabled // -> Disable Frame Embedding
res.set('X-XSS-Protection', '1; mode=block'); res.set('X-Frame-Options', 'deny')
//-> Disable MIME-sniffing // -> Re-enable XSS Fitler if disabled
res.set('X-Content-Type-Options', 'nosniff'); res.set('X-XSS-Protection', '1; mode=block')
//-> Disable IE Compatibility Mode // -> Disable MIME-sniffing
res.set('X-UA-Compatible', 'IE=edge'); res.set('X-Content-Type-Options', 'nosniff')
return next(); // -> Disable IE Compatibility Mode
res.set('X-UA-Compatible', 'IE=edge')
}; return next()
\ No newline at end of file }
"use strict"; 'use strict'
/** /**
* BruteForce schema * BruteForce schema
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
* @type {<Mongoose.Schema>} * @type {<Mongoose.Schema>}
*/ */
var bruteForceSchema = Mongoose.Schema({ var bruteForceSchema = Mongoose.Schema({
_id: { type: String, index: 1 }, _id: { type: String, index: 1 },
data: { data: {
count: Number, count: Number,
lastRequest: Date, lastRequest: Date,
firstRequest: Date firstRequest: Date
}, },
expires: { type: Date, index: { expires: '1d' } } expires: { type: Date, index: { expires: '1d' } }
}); })
module.exports = Mongoose.model('Bruteforce', bruteForceSchema); module.exports = Mongoose.model('Bruteforce', bruteForceSchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Entry schema * Entry schema
...@@ -10,7 +7,7 @@ const Promise = require('bluebird'), ...@@ -10,7 +7,7 @@ const Promise = require('bluebird'),
*/ */
var entrySchema = Mongoose.Schema({ var entrySchema = Mongoose.Schema({
_id: String, _id: String,
title: { title: {
type: String, type: String,
...@@ -31,9 +28,9 @@ var entrySchema = Mongoose.Schema({ ...@@ -31,9 +28,9 @@ var entrySchema = Mongoose.Schema({
} }
}, },
{ {
timestamps: {} timestamps: {}
}); })
entrySchema.index({ entrySchema.index({
_id: 'text', _id: 'text',
...@@ -48,6 +45,6 @@ entrySchema.index({ ...@@ -48,6 +45,6 @@ entrySchema.index({
content: 1 content: 1
}, },
name: 'EntriesTextIndex' name: 'EntriesTextIndex'
}); })
module.exports = Mongoose.model('Entry', entrySchema); module.exports = Mongoose.model('Entry', entrySchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Upload File schema * Upload File schema
...@@ -10,7 +7,7 @@ const Promise = require('bluebird'), ...@@ -10,7 +7,7 @@ const Promise = require('bluebird'),
*/ */
var uplFileSchema = Mongoose.Schema({ var uplFileSchema = Mongoose.Schema({
_id: String, _id: String,
category: { category: {
type: String, type: String,
...@@ -42,9 +39,6 @@ var uplFileSchema = Mongoose.Schema({ ...@@ -42,9 +39,6 @@ var uplFileSchema = Mongoose.Schema({
required: true required: true
} }
}, }, { timestamps: {} })
{
timestamps: {}
});
module.exports = Mongoose.model('UplFile', uplFileSchema); module.exports = Mongoose.model('UplFile', uplFileSchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Upload Folder schema * Upload Folder schema
...@@ -10,16 +7,13 @@ const Promise = require('bluebird'), ...@@ -10,16 +7,13 @@ const Promise = require('bluebird'),
*/ */
var uplFolderSchema = Mongoose.Schema({ var uplFolderSchema = Mongoose.Schema({
_id: String, _id: String,
name: { name: {
type: String, type: String,
index: true index: true
} }
}, }, { timestamps: {} })
{
timestamps: {}
});
module.exports = Mongoose.model('UplFolder', uplFolderSchema); module.exports = Mongoose.model('UplFolder', uplFolderSchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'), const Promise = require('bluebird')
bcrypt = require('bcryptjs-then'), const bcrypt = require('bcryptjs-then')
_ = require('lodash'); const _ = require('lodash')
/** /**
* Region schema * Region schema
...@@ -11,78 +11,73 @@ const Promise = require('bluebird'), ...@@ -11,78 +11,73 @@ const Promise = require('bluebird'),
*/ */
var userSchema = Mongoose.Schema({ var userSchema = Mongoose.Schema({
email: { email: {
type: String, type: String,
required: true, required: true,
index: true index: true
}, },
provider: { provider: {
type: String, type: String,
required: true required: true
}, },
providerId: { providerId: {
type: String type: String
}, },
password: { password: {
type: String type: String
}, },
name: { name: {
type: String type: String
}, },
rights: [{ rights: [{
role: String, role: String,
path: String, path: String,
exact: Boolean, exact: Boolean,
deny: Boolean deny: Boolean
}] }]
}, }, { timestamps: {} })
{
timestamps: {}
});
userSchema.statics.processProfile = (profile) => { userSchema.statics.processProfile = (profile) => {
let primaryEmail = ''
if (_.isArray(profile.emails)) {
let e = _.find(profile.emails, ['primary', true])
primaryEmail = (e) ? e.value : _.first(profile.emails).value
} else if (_.isString(profile.email) && profile.email.length > 5) {
primaryEmail = profile.email
} else {
return Promise.reject(new Error('Invalid User Email'))
}
let primaryEmail = ''; return db.User.findOneAndUpdate({
if(_.isArray(profile.emails)) { email: primaryEmail,
let e = _.find(profile.emails, ['primary', true]); provider: profile.provider
primaryEmail = (e) ? e.value : _.first(profile.emails).value; }, {
} else if(_.isString(profile.email) && profile.email.length > 5) { email: primaryEmail,
primaryEmail = profile.email; provider: profile.provider,
} else { providerId: profile.id,
return Promise.reject(new Error('Invalid User Email')); name: profile.displayName || _.split(primaryEmail, '@')[0]
} }, {
new: true,
return db.User.findOneAndUpdate({ upsert: true
email: primaryEmail, }).then((user) => {
provider: profile.provider return user || Promise.reject(new Error('User Upsert failed.'))
}, { })
email: primaryEmail, }
provider: profile.provider,
providerId: profile.id,
name: profile.displayName || _.split(primaryEmail, '@')[0]
}, {
new: true,
upsert: true
}).then((user) => {
return (user) ? user : Promise.reject(new Error('User Upsert failed.'));
});
};
userSchema.statics.hashPassword = (rawPwd) => { userSchema.statics.hashPassword = (rawPwd) => {
return bcrypt.hash(rawPwd); return bcrypt.hash(rawPwd)
}; }
userSchema.methods.validatePassword = function(rawPwd) { userSchema.methods.validatePassword = function (rawPwd) {
return bcrypt.compare(rawPwd, this.password).then((isValid) => { return bcrypt.compare(rawPwd, this.password).then((isValid) => {
return (isValid) ? true : Promise.reject(new Error('Invalid Login')); return (isValid) ? true : Promise.reject(new Error('Invalid Login'))
}); })
}; }
module.exports = Mongoose.model('User', userSchema); module.exports = Mongoose.model('User', userSchema)
\ No newline at end of file
...@@ -129,5 +129,35 @@ ...@@ -129,5 +129,35 @@
"twemoji-awesome": "^1.0.4", "twemoji-awesome": "^1.0.4",
"vue": "^2.1.10" "vue": "^2.1.10"
}, },
"standard": {
"globals": [
"app",
"appconfig",
"appdata",
"bgAgent",
"db",
"entries",
"git",
"mark",
"lang",
"lcdata",
"rights",
"upl",
"winston",
"ws",
"Mongoose",
"CORE_PATH",
"ROOTPATH",
"IS_DEBUG",
"PROCNAME",
"WSInternalKey"
],
"ignore": [
"assets/**/*",
"data/**/*",
"node_modules/**/*",
"repo/**/*"
]
},
"snyk": true "snyk": true
} }
'use strict'
// TODO
"use strict";
let path = require('path'),
fs = require('fs');
// ========================================
// Load global modules
// ========================================
global._ = require('lodash');
global.winston = require('winston');
\ No newline at end of file
...@@ -19,20 +19,15 @@ html ...@@ -19,20 +19,15 @@ html
// CSS // CSS
link(type='text/css', rel='stylesheet', href='/css/libs.css') link(type='text/css', rel='stylesheet', href='/css/libs.css')
link(type='text/css', rel='stylesheet', href='/css/app.css') link(type='text/css', rel='stylesheet', href='/css/error.css')
body(class='server-error') body(class='is-error')
section.hero.is-warning.is-fullheight .container
.hero-body a(href='/'): img(src='/favicons/android-icon-96x96.png')
.container h1= message
a(href='/'): img(src='/favicons/android-icon-96x96.png') h2 Oops, something went wrong
h1.title(style={ 'margin-top': '30px'})= message a.button.is-amber.is-inverted.is-featured(href='/') Go Home
h2.subtitle(style={ 'margin-bottom': '50px'}) Oops, something went wrong
a.button.is-warning.is-inverted(href='/') Go Home
if error.stack if error.stack
section.section h3 Detailed debug trail:
.container.is-fluid pre: code #{error.stack}
.content \ No newline at end of file
h3 Detailed debug trail:
pre: code #{error.stack}
\ No newline at end of file
...@@ -53,6 +53,11 @@ block content ...@@ -53,6 +53,11 @@ block content
a(href='/admin') a(href='/admin')
i.icon-head i.icon-head
span Account span Account
else
li
a(href='/login')
i.icon-unlock
span Login
aside.stickyscroll(data-margin-top=40) aside.stickyscroll(data-margin-top=40)
.sidebar-label .sidebar-label
i.icon-th-list i.icon-th-list
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment