diff --git a/server/api/admin.js b/server/api/admin.js index 65ecb56..2866b4d 100644 --- a/server/api/admin.js +++ b/server/api/admin.js @@ -19,6 +19,27 @@ function cleanUserObject (dbe) { } } +async function cleanClientObject (dbe) { + let user = await Users.User.get(dbe.user_id) + return { + id: dbe.id, + title: dbe.title, + description: dbe.description, + url: dbe.url, + redirect_url: dbe.redirect_url, + grants: dbe.grants, + icon: dbe.icon, + user: { + id: user.id, + display_name: user.display_name + }, + scope: dbe.scope, + secret: dbe.secret, + verified: dbe.verified === 1, + created_at: dbe.created_at + } +} + const API = { getAllUsers: async function (page) { let count = await Models.User.query().count('id as ids') @@ -41,6 +62,103 @@ const API = { page: paginated, users: users } + }, + getAllClients: async function (page) { + let count = await Models.OAuth2Client.query().count('id as ids') + if (!count.length || !count[0]['ids'] || isNaN(page)) { + return {error: 'No clients found'} + } + + count = count[0].ids + let paginated = Users.Pagination(perPage, parseInt(count), page) + let raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage) + + let clients = [] + for (let i in raw) { + let entry = raw[i] + + clients.push(await cleanClientObject(entry)) + } + + return { + page: paginated, + clients: clients + } + }, + getClient: async function (id) { + let raw = await Models.OAuth2Client.query().where('id', id) + if (!raw.length) return null + + return cleanClientObject(raw[0]) + }, + updateClient: async function (id, data) { + if (isNaN(id)) return {error: 'Invalid client ID'} + + let fields = [ + 'title', 'description', 'url', 'redirect_url', 'scope' + ] + + for (let i in data) { + if (fields.indexOf(i) === -1) { + delete data[i] + } + } + + for (let i in fields) { + if (!data[fields[i]] && fields[i] !== 'scope') return {error: 'Missing fields'} + } + + try { + await Models.OAuth2Client.query().patchAndFetchById(id, data) + } catch (e) { + return {error: 'No such client'} + } + + return {} + }, + newSecret: async function (id) { + if (isNaN(id)) return {error: 'Invalid client ID'} + let secret = Users.Hash(16) + + try { + await Models.OAuth2Client.query().patchAndFetchById(id, {secret: secret}) + } catch (e) { + return {error: 'No such client'} + } + + return {} + }, + createClient: async function (data, user) { + let fields = [ + 'title', 'description', 'url', 'redirect_url', 'scope' + ] + + for (let i in data) { + if (fields.indexOf(i) === -1) { + delete data[i] + } + } + + for (let i in fields) { + if (!data[fields[i]] && fields[i] !== 'scope') return {error: 'Missing fields'} + } + + let obj = Object.assign({ + secret: Users.Hash(16), + grants: 'authorization_code', + created_at: new Date(), + user_id: user.id + }, data) + + return Models.OAuth2Client.query().insert(obj) + }, + removeClient: async function (id) { + if (isNaN(id)) return {error: 'Invalid number'} + await Models.OAuth2Client.query().delete().where('id', id) + await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id) + await Models.OAuth2AccessToken.query().delete().where('client_id', id) + await Models.OAuth2RefreshToken.query().delete().where('client_id', id) + return true } } diff --git a/server/routes/admin.js b/server/routes/admin.js index 4262ced..ff41940 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -30,7 +30,7 @@ router.use(wrap(async (req, res, next) => { * ================ */ -apiRouter.get('/access', (req, res) => { +router.get('/access', (req, res) => { if (!req.session.accesstime || req.session.accesstime < Date.now()) { return res.status(401).jsonp({error: 'Access expired'}) } @@ -61,7 +61,11 @@ router.post('/', wrap(async (req, res, next) => { // Ensure that the admin panel is not kept open for prolonged time router.use(wrap(async (req, res, next) => { if (req.session.accesstime) { - if (req.session.accesstime > Date.now()) return next() + if (req.session.accesstime > Date.now()) { + req.session.accesstime = Date.now() + 300000 + return next() + } + delete req.session.accesstime } @@ -96,6 +100,88 @@ apiRouter.get('/users', wrap(async (req, res) => { res.jsonp(users) })) +apiRouter.get('/clients', wrap(async (req, res) => { + let page = parseInt(req.query.page) + if (isNaN(page) || page < 1) { + page = 1 + } + + let clients = await API.getAllClients(page) + res.jsonp(clients) +})) + +apiRouter.get('/client/:id', wrap(async (req, res) => { + let id = parseInt(req.params.id) + if (isNaN(id)) { + return res.status(400).jsonp({error: 'Invalid number'}) + } + + let client = await API.getClient(id) + if (!client) return res.status(400).jsonp({error: 'Invalid client'}) + + res.jsonp(client) +})) + +apiRouter.post('/client/new', wrap(async (req, res) => { + if (req.body.csrf !== req.session.csrf) { + return res.status(400).jsonp({error: 'Invalid session'}) + } + + let update = await API.createClient(req.body, req.session.user) + if (update.error) { + return res.status(400).jsonp({error: update.error}) + } + + res.status(204).end() +})) + +apiRouter.post('/client/update', wrap(async (req, res) => { + if (!req.body.id) return res.status(400).jsonp({error: 'ID missing'}) + if (req.body.csrf !== req.session.csrf) { + return res.status(400).jsonp({error: 'Invalid session'}) + } + + let update = await API.updateClient(parseInt(req.body.id), req.body) + if (update.error) { + return res.status(400).jsonp({error: update.error}) + } + + res.status(204).end() +})) + +apiRouter.post('/client/new_secret/:id', wrap(async (req, res) => { + let id = parseInt(req.params.id) + if (isNaN(id)) { + return res.status(400).jsonp({error: 'Invalid number'}) + } + + let client = await API.newSecret(id) + if (client.error) { + return res.status(400).jsonp({error: client.error}) + } + + res.jsonp(client) +})) + +apiRouter.post('/client/delete/:id', wrap(async (req, res) => { + let id = parseInt(req.params.id) + if (isNaN(id)) { + return res.status(400).jsonp({error: 'Invalid number'}) + } + + let client = await API.removeClient(id) + if (client.error) { + return res.status(400).jsonp({error: client.error}) + } + + res.jsonp(client) +})) + +apiRouter.use((err, req, res, next) => { + console.error(err) + return res.status(500).jsonp({error: 'Internal server error'}) +}) + router.use('/api', apiRouter) module.exports = router diff --git a/src/script/admin.js b/src/script/admin.js index 732861d..25cedc5 100644 --- a/src/script/admin.js +++ b/src/script/admin.js @@ -55,14 +55,154 @@ function loadUsers (page) { }) } +function editClient (id) { + $.ajax({ + type: 'get', + url: '/admin/api/client/' + id, + success: function (data) { + window.Dialog.openTemplate('Editing client', 'clientEdit', data) + $('#ffsubmit').submit(function (e) { + e.preventDefault() + $.ajax({ + type: 'post', + url: '/admin/api/client/update', + data: $(this).serialize(), + success: function (data) { + window.Dialog.close() + loadClients(1) + }, + error: function (e) { + if (e.responseJSON && e.responseJSON.error) { + $('form .message').show() + $('form .message').text(e.responseJSON.error) + } + } + }) + }) + } + }) +} + +function deleteClient (id) { + window.Dialog.openTemplate('Deleting client', 'clientRemove') + $('#fremove').click(function (e) { + $.post({ + url: '/admin/api/client/delete/' + id, + success: function (data) { + window.Dialog.close() + loadClients(1) + } + }) + }) +} + +function loadClients (page) { + $.ajax({ + type: 'get', + url: '/admin/api/clients', + data: {page: page}, + success: function (data) { + $('#clientlist').html('') + if (data.error) { + $('#clientlist').html('
') + return + } + + var pgbtn = paginationButton(data.page) + $('#clientlist').append(pgbtn) + $('.pgn .button').click(function (e) { + var pgnum = $(this).data('page') + if (pgnum == null) return + loadClients(parseInt(pgnum)) + }) + + for (var u in data.clients) { + var client = data.clients[u] + client.created_at = new Date(client.created_at) + var tmp = buildTemplateScript('client', client) + $('#clientlist').append(tmp) + } + + $('.edit').click(function (e) { + var client = $(this).data('client') + editClient(parseInt(client)) + }) + + $('.delete').click(function (e) { + var client = $(this).data('client') + deleteClient(parseInt(client)) + }) + + $('.newsecret').click(function (e) { + var client = $(this).data('client') + $.post({ + url: '/admin/api/client/new_secret/' + parseInt(client), + success: function (e) { + loadClients(1) + } + }) + }) + } + }) +} + $(document).ready(function () { + window.Dialog = $('#dialog') + window.Dialog.open = function (title, content, pad) { + $('#dialog #title').text(title) + if (pad) { + content = 'Are you sure?
+ +