show oauth2 authorizations on user settings page

This commit is contained in:
Evert Prants 2017-08-26 12:47:37 +03:00
parent 27668e8f2e
commit 9b7bc571e0
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
14 changed files with 178 additions and 28 deletions

View File

@ -297,6 +297,47 @@ const API = {
return {error: null, user: user} return {error: null, user: user}
} }
},
OAuth2: {
getUserAuthorizations: async function (user) {
user = await API.User.ensureObject(user)
let auths = await models.OAuth2AuthorizedClient.query().where('user_id', user.id)
let nicelist = []
for (let i in auths) {
let auth = auths[i]
let client = await models.OAuth2Client.query().where('id', auth.client_id)
if (!client.length) continue
client = client[0]
let obj = {
id: client.id,
title: client.title,
description: client.description,
url: client.url,
icon: client.icon,
scope: client.scope.split(' '),
created_at: auth.created_at,
expires_at: auth.expires_at
}
nicelist.push(obj)
}
return nicelist
},
removeUserAuthorization: async function (user, clientId) {
user = await API.User.ensureObject(user)
let auth = await models.OAuth2AuthorizedClient.query().where('user_id', user.id).andWhere('client_id', clientId)
if (!auth.length) return false
for (let i in auth) {
await models.OAuth2AuthorizedClient.query().delete().where('id', auth[i].id)
}
return true
}
} }
} }
} }

View File

@ -9,7 +9,7 @@ import News from '../api/news'
import Image from '../api/image' import Image from '../api/image'
import APIExtern from '../api/external' import APIExtern from '../api/external'
const userContent = path.join(__dirname, '../..', 'usercontent') // const userContent = path.join(__dirname, '../..', 'usercontent')
let router = express.Router() let router = express.Router()
@ -289,6 +289,11 @@ router.get('/news', wrap(async (req, res) => {
res.jsonp(articles) res.jsonp(articles)
})) }))
/* ==========
* AVATAR
* ==========
*/
// Promisify multiparty form parser
async function promiseForm (req) { async function promiseForm (req) {
let form = new multiparty.Form() let form = new multiparty.Form()
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -299,6 +304,7 @@ async function promiseForm (req) {
}) })
} }
// Upload avatar image
router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => { router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let data = await promiseForm(req) let data = await promiseForm(req)
@ -320,6 +326,7 @@ router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => {
res.status(200).jsonp({}) res.status(200).jsonp({})
})) }))
// Remove avatar image
router.post('/avatar/remove', wrap(async (req, res, next) => { router.post('/avatar/remove', wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
@ -329,6 +336,7 @@ router.post('/avatar/remove', wrap(async (req, res, next) => {
res.status(200).jsonp({done: true}) res.status(200).jsonp({done: true})
})) }))
// Get latest avatar of logged in user
router.get('/avatar', wrap(async (req, res, next) => { router.get('/avatar', wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let user = req.session.user let user = req.session.user
@ -339,6 +347,7 @@ router.get('/avatar', wrap(async (req, res, next) => {
res.redirect('/usercontent/images/' + user.avatar_file) res.redirect('/usercontent/images/' + user.avatar_file)
})) }))
// Get latest avatar of user by id
router.get('/avatar/:id', wrap(async (req, res, next) => { router.get('/avatar/:id', wrap(async (req, res, next) => {
let id = parseInt(req.params.id) let id = parseInt(req.params.id)
if (isNaN(id)) return next() if (isNaN(id)) return next()
@ -351,10 +360,37 @@ router.get('/avatar/:id', wrap(async (req, res, next) => {
res.redirect('/usercontent/images/' + user.avatar_file) res.redirect('/usercontent/images/' + user.avatar_file)
})) }))
// Redirect to no avatar on 404
router.use('/avatar', (req, res) => { router.use('/avatar', (req, res) => {
res.redirect('/static/image/avatar.png') res.redirect('/static/image/avatar.png')
}) })
/* =====================
* OAuth2 Management
* =====================
*/
router.get('/oauth2/authorized-clients', wrap(async (req, res, next) => {
if (!req.session.user) return next()
let list = await API.User.OAuth2.getUserAuthorizations(req.session.user)
if (!list) return next()
res.jsonp(list)
}))
router.post('/oauth2/authorized-clients/delete', wrap(async (req, res, next) => {
if (!req.session.user) return next()
let clientId = parseInt(req.body.client_id)
if (isNaN(clientId)) return res.status(400).jsonp({error: 'Missing Client ID parameter'})
let done = await API.User.OAuth2.removeUserAuthorization(req.session.user, clientId)
if (!done) return res.status(400).jsonp({error: 'Failed to remove client authorization'})
res.status(204).end()
}))
// 404 // 404
router.use((req, res) => { router.use((req, res) => {
res.status(404).jsonp({error: 'Not found'}) res.status(404).jsonp({error: 'Not found'})

View File

@ -102,7 +102,7 @@ router.get('/login', extraButtons, (req, res) => {
return res.redirect(uri) return res.redirect(uri)
} }
res.render('login') res.render('user/login')
}) })
router.get('/register', extraButtons, (req, res) => { router.get('/register', extraButtons, (req, res) => {
@ -121,7 +121,7 @@ router.get('/register', extraButtons, (req, res) => {
res.locals.recaptcha = config.security.recaptcha.site_key res.locals.recaptcha = config.security.recaptcha.site_key
} }
res.render('register') res.render('user/register')
}) })
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => { router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
@ -131,18 +131,18 @@ router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
let newToken = await API.User.Login.totpAquire(req.session.user) let newToken = await API.User.Login.totpAquire(req.session.user)
if (!newToken) return res.redirect('/') if (!newToken) return res.redirect('/')
res.render('totp', { uri: newToken }) res.render('user/totp', { uri: newToken })
})) }))
router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => { router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => {
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('/')
res.render('password') res.render('user/password')
})) }))
router.get('/login/verify', (req, res) => { router.get('/login/verify', (req, res) => {
res.render('totp-check') res.render('user/totp-check')
}) })
router.get('/user/manage', ensureLogin, wrap(async (req, res) => { router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
@ -177,11 +177,11 @@ router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
} }
} }
res.render('settings', {totp: totpEnabled, password: socialStatus.password}) res.render('user/settings', {totp: totpEnabled, password: socialStatus.password})
})) }))
router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => { router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => {
res.render('password_new') res.render('user/password_new')
})) }))
router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => { router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => {
@ -194,7 +194,7 @@ router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => {
let socialStatus = await API.User.socialStatus(req.session.user) let socialStatus = await API.User.socialStatus(req.session.user)
res.render('email_change', {email: obfuscated, password: socialStatus.password}) res.render('user/email_change', {email: obfuscated, password: socialStatus.password})
})) }))
/* /*
@ -622,7 +622,7 @@ router.get('/news/', wrap(async (req, res) => {
router.get('/partials/:view', wrap(async (req, res, next) => { router.get('/partials/:view', wrap(async (req, res, next) => {
if (!req.params.view) return next() if (!req.params.view) return next()
res.render('partials/' + req.params.view) res.render('user/partials/' + req.params.view)
})) }))
/* /*

View File

@ -36,6 +36,61 @@ $(document).ready(function () {
return newWindow return newWindow
} }
function removeAuthorization (clientId) {
$.ajax({
type: 'post',
url: '/api/oauth2/authorized-clients/delete',
data: { client_id: clientId },
success: function (data) {
loadAuthorizations()
}
})
}
function loadAuthorizations () {
$.get({
url: '/api/oauth2/authorized-clients',
dataType: 'json',
success: function (data) {
if (!data.length) {
return $('#clientlist').html('There is nothing to show at this moment.')
}
var html = ''
for (var i in data) {
var client = data[i]
html += '<div class="authclient application" data-client-id="' + client.id + '" id="client-' + client.id + '">'
html += '<div class="remove" id="deleteclient"><i class="fa fa-fw fa-ban"></i></div>'
html += '<div class="picture">'
if (client.icon) {
html += '<img src="' + client.icon + '">'
} else {
html += '<div class="noicon"><i class="fa fa-fw fa-gears"></i></div>'
}
html += '</div>'
html += '<div class="info">'
html += '<div class="name">' + client.title + '</div>'
html += '<div class="description">' + client.description + '</div>'
html += '<a class="url" href="' + client.url + '">' + client.url + '</a>'
html += '<div class="timestamp">Authorized ' + new Date(client.created_at) + '</div>'
html += '</div></div>'
}
$('#clientlist').html(html)
for (let i in data) {
$('#client-' + data[i].id + ' #deleteclient').click(function (e) {
let clid = $(this).parent().data('client-id')
if (clid != null) {
removeAuthorization(clid)
}
})
}
}
})
}
window.Dialog = $('#dialog') window.Dialog = $('#dialog')
window.Dialog.open = function (title, content, pad) { window.Dialog.open = function (title, content, pad) {
$('#dialog #title').text(title) $('#dialog #title').text(title)
@ -152,6 +207,10 @@ $(document).ready(function () {
}) })
} }
if ($('#clientlist').length) {
loadAuthorizations()
}
if ($('#newAvatar').length) { if ($('#newAvatar').length) {
$('#newAvatar').click(function (e) { $('#newAvatar').click(function (e) {
e.preventDefault() e.preventDefault()

View File

@ -237,6 +237,7 @@ input:not([type="submit"])
background-color: #fff background-color: #fff
box-shadow: 5px 5px 15px #868686 box-shadow: 5px 5px 15px #868686
border: 1px solid #ddd border: 1px solid #ddd
margin-bottom: 10%
h1:first-child, h2:first-child, h3:first-child h1:first-child, h2:first-child, h3:first-child
margin-top: 0 margin-top: 0
.left, .right .left, .right
@ -360,6 +361,11 @@ span.divider
.application .application
height: 140px height: 140px
#deleteclient
float: right
color: red
font-size: 120%
cursor: pointer
.picture .picture
width: 120px width: 120px
height: 120px height: 120px
@ -387,6 +393,7 @@ span.divider
display: block display: block
text-overflow: ellipsis text-overflow: ellipsis
overflow: hidden overflow: hidden
max-width: 200px
input.authorize input.authorize
background-color: #00b0ff background-color: #00b0ff
@ -421,12 +428,13 @@ input.invalid
margin-top: 20px margin-top: 20px
margin-bottom: 20px margin-bottom: 20px
.newsfeed span.load
.load
font-size: 120% font-size: 120%
span span
vertical-align: super vertical-align: super
margin-left: 10px margin-left: 10px
.newsfeed
.prvarticle .prvarticle
margin-bottom: 10px margin-bottom: 10px
.title .title

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - Change User Email |Icy Network - Change User Email

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - Log In |Icy Network - Log In
@ -24,4 +24,4 @@ block body
input(type="submit", value="Log in") input(type="submit", value="Log in")
a#create(href="/register") Create an account a#create(href="/register") Create an account
.right .right
include includes/external.pug include ../includes/external.pug

View File

@ -4,7 +4,7 @@
.otherdata .otherdata
h3 Current Avatar h3 Current Avatar
.avatar .avatar
include ../includes/avatar.pug include ../../includes/avatar.pug
.inputting .inputting
h3 Upload new h3 Upload new
.message.error .message.error

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - Password Required |Icy Network - Password Required

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - Change User Password |Icy Network - Change User Password

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - Register |Icy Network - Register
@ -34,4 +34,4 @@ block body
input(type="submit", value="Register") input(type="submit", value="Register")
a#create(href="/login") Log in with an existing account a#create(href="/login") Log in with an existing account
.right .right
include includes/external.pug include ../includes/external.pug

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - User Settings |Icy Network - User Settings
@ -23,7 +23,7 @@ block body
input(type="text", name="display_name", id="display_name", value=user.display_name) input(type="text", name="display_name", id="display_name", value=user.display_name)
label Avatar label Avatar
.avatarCont .avatarCont
include includes/avatar.pug include ../includes/avatar.pug
.options .options
a#newAvatar(href='#') Change Avatar a#newAvatar(href='#') Change Avatar
if user.avatar_file if user.avatar_file
@ -31,7 +31,7 @@ block body
input(type="submit", value="Save Settings") input(type="submit", value="Save Settings")
.right .right
h3 Social Media Accounts h3 Social Media Accounts
include includes/external.pug include ../includes/external.pug
if twitter_auth == false if twitter_auth == false
a.option.accdisconnect(href="/api/external/twitter/remove") a.option.accdisconnect(href="/api/external/twitter/remove")
i.fa.fa-fw.fa-times i.fa.fa-fw.fa-times
@ -60,3 +60,9 @@ block body
a.option(href="/user/manage/email") a.option(href="/user/manage/email")
i.fa.fa-fw.fa-envelope i.fa.fa-fw.fa-envelope
|Change Email Address |Change Email Address
.clients
h2 OAuth2 Authorized Clients
.cl#clientlist
span.load
i.fa.fa-spin.fa-spinner.fa-2x
span Loading list

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - Log In - Verification Required |Icy Network - Log In - Verification Required

View File

@ -1,4 +1,4 @@
extends layout.pug extends ../layout.pug
block title block title
|Icy Network - Activate Authenticator |Icy Network - Activate Authenticator