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}
}
},
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 APIExtern from '../api/external'
const userContent = path.join(__dirname, '../..', 'usercontent')
// const userContent = path.join(__dirname, '../..', 'usercontent')
let router = express.Router()
@ -289,6 +289,11 @@ router.get('/news', wrap(async (req, res) => {
res.jsonp(articles)
}))
/* ==========
* AVATAR
* ==========
*/
// Promisify multiparty form parser
async function promiseForm (req) {
let form = new multiparty.Form()
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) => {
if (!req.session.user) return next()
let data = await promiseForm(req)
@ -320,6 +326,7 @@ router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => {
res.status(200).jsonp({})
}))
// Remove avatar image
router.post('/avatar/remove', wrap(async (req, res, 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})
}))
// Get latest avatar of logged in user
router.get('/avatar', wrap(async (req, res, next) => {
if (!req.session.user) return next()
let user = req.session.user
@ -339,6 +347,7 @@ router.get('/avatar', wrap(async (req, res, next) => {
res.redirect('/usercontent/images/' + user.avatar_file)
}))
// Get latest avatar of user by id
router.get('/avatar/:id', wrap(async (req, res, next) => {
let id = parseInt(req.params.id)
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)
}))
// Redirect to no avatar on 404
router.use('/avatar', (req, res) => {
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
router.use((req, res) => {
res.status(404).jsonp({error: 'Not found'})

View File

@ -102,7 +102,7 @@ router.get('/login', extraButtons, (req, res) => {
return res.redirect(uri)
}
res.render('login')
res.render('user/login')
})
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.render('register')
res.render('user/register')
})
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)
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) => {
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
if (!twoFaEnabled) return res.redirect('/')
res.render('password')
res.render('user/password')
}))
router.get('/login/verify', (req, res) => {
res.render('totp-check')
res.render('user/totp-check')
})
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) => {
res.render('password_new')
res.render('user/password_new')
}))
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)
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) => {
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
}
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.open = function (title, content, pad) {
$('#dialog #title').text(title)
@ -152,6 +207,10 @@ $(document).ready(function () {
})
}
if ($('#clientlist').length) {
loadAuthorizations()
}
if ($('#newAvatar').length) {
$('#newAvatar').click(function (e) {
e.preventDefault()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
extends layout.pug
extends ../layout.pug
block title
|Icy Network - User Settings
@ -23,7 +23,7 @@ block body
input(type="text", name="display_name", id="display_name", value=user.display_name)
label Avatar
.avatarCont
include includes/avatar.pug
include ../includes/avatar.pug
.options
a#newAvatar(href='#') Change Avatar
if user.avatar_file
@ -31,7 +31,7 @@ block body
input(type="submit", value="Save Settings")
.right
h3 Social Media Accounts
include includes/external.pug
include ../includes/external.pug
if twitter_auth == false
a.option.accdisconnect(href="/api/external/twitter/remove")
i.fa.fa-fw.fa-times
@ -60,3 +60,9 @@ block body
a.option(href="/user/manage/email")
i.fa.fa-fw.fa-envelope
|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
|Icy Network - Log In - Verification Required

View File

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