diff --git a/server/api/index.js b/server/api/index.js index c5baed3..b389436 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -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 + } } } } diff --git a/server/routes/api.js b/server/routes/api.js index f433a80..2fc9774 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -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'}) diff --git a/server/routes/index.js b/server/routes/index.js index 89bea9d..4eaf15c 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -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) })) /* diff --git a/src/script/main.js b/src/script/main.js index b5e877d..8065132 100644 --- a/src/script/main.js +++ b/src/script/main.js @@ -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 += '