some improvements, jquery dialog

This commit is contained in:
Evert Prants 2017-08-25 14:37:34 +03:00
parent 9441bfc74c
commit 4c7f72f65c
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
6 changed files with 159 additions and 32 deletions

View File

@ -85,6 +85,12 @@ function extraButtons (req, res, next) {
next() next()
} }
function ensureLogin (req, res, next) {
if (req.session.user) return next()
req.session.redirectUri = req.originalUrl
res.redirect('/login')
}
router.get('/login', extraButtons, (req, res) => { router.get('/login', extraButtons, (req, res) => {
if (req.session.user) { if (req.session.user) {
let uri = '/' let uri = '/'
@ -118,8 +124,7 @@ router.get('/register', extraButtons, (req, res) => {
res.render('register') res.render('register')
}) })
router.get('/user/two-factor', wrap(async (req, res) => { router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login')
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user) let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
if (twoFaEnabled) return res.redirect('/') if (twoFaEnabled) return res.redirect('/')
@ -129,8 +134,7 @@ router.get('/user/two-factor', wrap(async (req, res) => {
res.render('totp', { uri: newToken }) res.render('totp', { uri: newToken })
})) }))
router.get('/user/two-factor/disable', wrap(async (req, res) => { router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login')
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user) let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
if (!twoFaEnabled) return res.redirect('/') if (!twoFaEnabled) return res.redirect('/')
@ -141,9 +145,7 @@ router.get('/login/verify', (req, res) => {
res.render('totp-check') res.render('totp-check')
}) })
router.get('/user/manage', wrap(async (req, res) => { router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login')
let totpEnabled = false let totpEnabled = false
let socialStatus = await API.User.socialStatus(req.session.user) let socialStatus = await API.User.socialStatus(req.session.user)
@ -178,15 +180,11 @@ router.get('/user/manage', wrap(async (req, res) => {
res.render('settings', {totp: totpEnabled, password: socialStatus.password}) res.render('settings', {totp: totpEnabled, password: socialStatus.password})
})) }))
router.get('/user/manage/password', wrap(async (req, res) => { router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login')
res.render('password_new') res.render('password_new')
})) }))
router.get('/user/manage/email', wrap(async (req, res) => { router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login')
let obfuscated = req.session.user.email let obfuscated = req.session.user.email
if (obfuscated) { if (obfuscated) {
let split = obfuscated.split('@') let split = obfuscated.split('@')
@ -216,7 +214,16 @@ function formError (req, res, error, redirect) {
res.redirect(redirect || parseurl(req).path) res.redirect(redirect || parseurl(req).path)
} }
router.post('/user/two-factor', wrap(async (req, res) => { function cleanString (input) {
let output = ''
for (let i = 0; i < input.length; i++) {
output += input.charCodeAt(i) <= 127 ? input.charAt(i) : ''
}
return output
}
router.post('/user/two-factor', wrap(async (req, res, next) => {
if (!req.session.user) return next()
if (!req.body.code) { if (!req.body.code) {
return formError(req, res, 'You need to enter the code.') return formError(req, res, 'You need to enter the code.')
} }
@ -233,7 +240,8 @@ router.post('/user/two-factor', wrap(async (req, res) => {
res.redirect('/') res.redirect('/')
})) }))
router.post('/user/two-factor/disable', wrap(async (req, res) => { router.post('/user/two-factor/disable', wrap(async (req, res, next) => {
if (!req.session.user) return next()
if (req.body.csrf !== req.session.csrf) { if (req.body.csrf !== req.session.csrf) {
return formError(req, res, 'Invalid session! Try reloading the page.') return formError(req, res, 'Invalid session! Try reloading the page.')
} }
@ -250,7 +258,8 @@ router.post('/user/two-factor/disable', wrap(async (req, res) => {
res.redirect('/') res.redirect('/')
})) }))
router.post('/login/verify', wrap(async (req, res) => { router.post('/login/verify', wrap(async (req, res, next) => {
if (req.session.user) return next()
if (req.session.totp_check === null) return res.redirect('/login') if (req.session.totp_check === null) return res.redirect('/login')
if (!req.body.code && !req.body.recovery) { if (!req.body.code && !req.body.recovery) {
return formError(req, res, 'You need to enter the code.') return formError(req, res, 'You need to enter the code.')
@ -291,8 +300,9 @@ router.post('/login/verify', wrap(async (req, res) => {
res.redirect(uri) res.redirect(uri)
})) }))
router.post('/login', wrap(async (req, res) => { router.post('/login', wrap(async (req, res, next) => {
if (!req.body.username || !req.body.password) { if (req.session.user) return next()
if (!req.body.username || !req.body.password || req.body.username === '') {
return res.redirect('/login') return res.redirect('/login')
} }
@ -303,6 +313,8 @@ router.post('/login', wrap(async (req, res) => {
let user = await API.User.get(req.body.username) let user = await API.User.get(req.body.username)
if (!user) return formError(req, res, 'Invalid username or password.') if (!user) return formError(req, res, 'Invalid username or password.')
if (!user.password || user.password === '') return formError(req, res, 'Please log in using the buttons on the right.')
let pwMatch = await API.User.Login.password(user, req.body.password) let pwMatch = await API.User.Login.password(user, req.body.password)
if (!pwMatch) return formError(req, res, 'Invalid username or password.') if (!pwMatch) return formError(req, res, 'Invalid username or password.')
@ -333,7 +345,8 @@ router.post('/login', wrap(async (req, res) => {
res.redirect(uri) res.redirect(uri)
})) }))
router.post('/register', accountLimiter, wrap(async (req, res) => { router.post('/register', accountLimiter, wrap(async (req, res, next) => {
if (req.session.user) return next()
if (!req.body.username || !req.body.display_name || !req.body.password || !req.body.email) { if (!req.body.username || !req.body.display_name || !req.body.password || !req.body.email) {
return formError(req, res, 'Please fill in all the fields.') return formError(req, res, 'Please fill in all the fields.')
} }
@ -344,7 +357,7 @@ router.post('/register', accountLimiter, wrap(async (req, res) => {
// 1st Check: Username Characters and length // 1st Check: Username Characters and length
let username = req.body.username let username = req.body.username
if (!username || !username.match(/^([\w-]{3,26})$/i)) { if (!username || !username.match(/^([\w-_]{3,26})$/i)) {
return formError(req, res, 'Invalid username! Must be between 3-26 characters and composed of alphanumeric characters!') return formError(req, res, 'Invalid username! Must be between 3-26 characters and composed of alphanumeric characters!')
} }
@ -398,7 +411,7 @@ router.post('/register', accountLimiter, wrap(async (req, res) => {
// Attempt to create the user // Attempt to create the user
let newUser = await API.User.Register.newAccount({ let newUser = await API.User.Register.newAccount({
username: username, username: username,
display_name: displayName, display_name: cleanString(displayName),
password: hash, password: hash,
email: email, email: email,
ip_address: req.realIP ip_address: req.realIP
@ -428,6 +441,8 @@ router.post('/user/manage', wrap(async (req, res, next) => {
return formError(req, res, 'Invalid display name!') return formError(req, res, 'Invalid display name!')
} }
displayName = cleanString(displayName)
// No change // No change
if (displayName === req.session.user.display_name) { if (displayName === req.session.user.display_name) {
return res.redirect('/user/manage') return res.redirect('/user/manage')
@ -458,7 +473,8 @@ router.post('/user/manage/password', wrap(async (req, res, next) => {
return formError(req, res, 'Please enter your current password.') return formError(req, res, 'Please enter your current password.')
} }
let passwordMatch = await API.User.Login.password(req.session.user, req.body.password_old) let user = req.session.user
let passwordMatch = await API.User.Login.password(user, req.body.password_old)
if (!passwordMatch) { if (!passwordMatch) {
return formError(req, res, 'The password you provided is incorrect.') return formError(req, res, 'The password you provided is incorrect.')
} }
@ -475,7 +491,7 @@ router.post('/user/manage/password', wrap(async (req, res, next) => {
password = await API.User.Register.hashPassword(password) password = await API.User.Register.hashPassword(password)
let success = await API.User.update(req.session.user, { let success = await API.User.update(user, {
password: password password: password
}) })
@ -483,7 +499,6 @@ router.post('/user/manage/password', wrap(async (req, res, next) => {
return formError(req, res, success.error) return formError(req, res, success.error)
} }
let user = req.session.user
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP) console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
if (config.email && config.email.enabled) { if (config.email && config.email.enabled) {
@ -557,7 +572,11 @@ router.get('/docs/:name', (req, res, next) => {
return next() return next()
} }
doc = fs.readFileSync(doc, {encoding: 'utf8'}) try {
doc = fs.readFileSync(doc, {encoding: 'utf8'})
} catch (e) {
return next(e)
}
res.render('document', {doc: doc}) res.render('document', {doc: doc})
}) })
@ -587,6 +606,12 @@ router.get('/news/', wrap(async (req, res) => {
res.render('news', {news: news}) res.render('news', {news: news})
})) }))
router.get('/partials/:view', wrap(async (req, res, next) => {
if (!req.params.view) return next()
res.render('partials/' + req.params.view)
}))
/* /*
========= =========
OTHER OTHER
@ -614,12 +639,34 @@ router.get('/activate/:token', wrap(async (req, res) => {
router.use('/api', apiRouter) router.use('/api', apiRouter)
/*
NO ROUTES BEYOND THIS POINT
*/
// Handle 'Failed to lookup view' errors
router.use((err, req, res, next) => {
if (err && err.stack) {
if (err.stack.indexOf('Failed to lookup view') !== -1) {
return next() // To 404
}
}
next(err) // To error handler
})
// 404 - last route
router.use((req, res) => { router.use((req, res) => {
res.status(404).render('404') res.status(404).render('404')
}) })
// Error handler
router.use((err, req, res, next) => { router.use((err, req, res, next) => {
console.error(err) console.error(err)
if (process.env.NODE_ENV !== 'production') {
return res.end(err.stack)
}
next() next()
}) })

View File

@ -2,7 +2,6 @@ import express from 'express'
import uapi from '../api' import uapi from '../api'
import OAuth2 from '../api/oauth2' import OAuth2 from '../api/oauth2'
import RateLimit from 'express-rate-limit' import RateLimit from 'express-rate-limit'
import config from '../../scripts/load-config'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
let router = express.Router() let router = express.Router()
@ -19,12 +18,9 @@ let oauthLimiter = new RateLimit({
router.use(oauthLimiter) router.use(oauthLimiter)
function ensureLoggedIn (req, res, next) { function ensureLoggedIn (req, res, next) {
if (req.session.user) { if (req.session.user) return next()
next() req.session.redirectUri = req.originalUrl
} else { res.redirect('/login')
req.session.redirectUri = req.originalUrl
res.redirect('/login')
}
} }
router.use('/authorize', ensureLoggedIn, oauth.controller.authorization) router.use('/authorize', ensureLoggedIn, oauth.controller.authorization)

View File

@ -15,6 +15,37 @@ $(document).ready(function () {
} }
} }
window.Dialog = $('#dialog')
window.Dialog.open = function (title, content, pad) {
$('#dialog #title').text(title)
if (pad) {
content = '<div class="pad">' + content + '</div>'
}
$('#dialog #content').html(content)
$('#dialog').fadeIn()
}
window.Dialog.close = function () {
$('#dialog').fadeOut('fast', function () {
$('#dialog #content').html('')
})
}
window.Dialog.openPartial = function (title, partial) {
$.get({
url: '/partials/' + partial,
success: function (html) {
window.Dialog.open(title, html, false)
}
}).fail(function (e) {
console.error(e)
})
}
$('#dialog #close').click(function (e) {
window.Dialog.close()
})
if (window.location.hash) { if (window.location.hash) {
var locha = window.location.hash var locha = window.location.hash
if ($(locha).length) { if ($(locha).length) {

View File

@ -441,6 +441,50 @@ input.invalid
.option .option
display: block display: block
.dialog-drop
display: block
position: fixed
display: none
top: 0
left: 0
bottom: 0
right: 0
background-color: rgba(56, 56, 56, 0.8)
z-index: 10
.dialog
min-width: 400px
width: fit-content
max-width: 50vw
min-height: 180px
height: fit-content
background-color: #fff
margin: auto
margin-top: 5%
transition: all 0.1s linear
overflow: hidden
.pad
padding: 5px
.head
height: 40px
background-color: #e6e6e6
box-shadow: 0px 3px #ddd
overflow: hidden
position: relative
#title
max-width: 80%
text-overflow: ellipsis
overflow: hidden
white-space: nowrap
margin: 12px
display: block
#close
position: absolute
top: 6px
right: 5px
color: #ff2525
cursor: pointer
padding: 5px
@media all and (max-width: 800px) @media all and (max-width: 800px)
.navigator .navigator
padding: 0 10px padding: 0 10px

View File

@ -15,6 +15,14 @@ html
.logo .logo
.part1 Icy .part1 Icy
.part2 Network .part2 Network
block dialog
.dialog-drop#dialog
.dialog
.head
#title
#close
i.fa.fa-fw.fa-times
.content#content
block nav block nav
.anchor .anchor
nav.navigator nav.navigator

View File

@ -0,0 +1 @@
img(src=user.avatar_file)