From 49652e6f1b96afe5a56a8e5cb45fcb898791d061 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 2 Oct 2020 15:34:57 -0400 Subject: [PATCH] feat: management of API tokens via ACP --- public/language/en-GB/admin/menu.json | 1 + public/language/en-GB/admin/settings/api.json | 10 ++++++ public/src/admin/settings/api.js | 36 +++++++++++++++++++ public/src/modules/settings/sorted-list.js | 1 + src/controllers/admin/settings.js | 1 - src/meta/settings.js | 2 ++ src/plugins/index.js | 23 ++++++++++++ src/routes/authentication.js | 13 +++++-- .../admin/partials/api/sorted-list/form.tpl | 14 ++++++++ .../admin/partials/api/sorted-list/item.tpl | 13 +++++++ src/views/admin/settings/api.tpl | 25 +++++++++++++ 11 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 public/language/en-GB/admin/settings/api.json create mode 100644 public/src/admin/settings/api.js create mode 100644 src/views/admin/partials/api/sorted-list/form.tpl create mode 100644 src/views/admin/partials/api/sorted-list/item.tpl create mode 100644 src/views/admin/settings/api.tpl diff --git a/public/language/en-GB/admin/menu.json b/public/language/en-GB/admin/menu.json index b6a38f0d0e..01cc355fa1 100644 --- a/public/language/en-GB/admin/menu.json +++ b/public/language/en-GB/admin/menu.json @@ -31,6 +31,7 @@ "settings/pagination": "Pagination", "settings/tags": "Tags", "settings/notifications": "Notifications", + "settings/api": "API Access", "settings/sounds": "Sounds", "settings/social": "Social", "settings/cookies": "Cookies", diff --git a/public/language/en-GB/admin/settings/api.json b/public/language/en-GB/admin/settings/api.json new file mode 100644 index 0000000000..fe2f282d4a --- /dev/null +++ b/public/language/en-GB/admin/settings/api.json @@ -0,0 +1,10 @@ +{ + "tokens": "Tokens", + "lead-text": "From this page you can configure access to the Write API in NodeBB.", + "intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.", + "docs": "Click here to access the full API specification", + + "uid": "User ID", + "uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter", + "description": "Description" +} \ No newline at end of file diff --git a/public/src/admin/settings/api.js b/public/src/admin/settings/api.js new file mode 100644 index 0000000000..d015d48679 --- /dev/null +++ b/public/src/admin/settings/api.js @@ -0,0 +1,36 @@ +'use strict'; + +define('admin/settings/api', ['settings'], function (settings) { + var ACP = {}; + + ACP.init = function () { + const saveEl = $('#save'); + settings.load('core.api', $('.core-api-settings')); + saveEl.off('click'); // override settingsv1 handling + $('#save').on('click', saveSettings); + + $(window).on('action:settings.sorted-list.loaded', (ev, { element }) => { + element.addEventListener('click', (ev) => { + if (ev.target.closest('input[readonly]')) { + // Select entire input text + ev.target.selectionStart = 0; + ev.target.selectionEnd = ev.target.value.length; + } + }); + }); + }; + + function saveSettings() { + settings.save('core.api', $('.core-api-settings'), function () { + app.alert({ + type: 'success', + alert_id: 'core.api-saved', + title: 'Settings Saved', + timeout: 5000, + }); + ajaxify.refresh(); + }); + } + + return ACP; +}); diff --git a/public/src/modules/settings/sorted-list.js b/public/src/modules/settings/sorted-list.js index 8710b508bd..4307cb6e83 100644 --- a/public/src/modules/settings/sorted-list.js +++ b/public/src/modules/settings/sorted-list.js @@ -60,6 +60,7 @@ define('settings/sorted-list', ['benchpress', 'jqueryui'], function (benchpress) }); $list.sortable().addClass('pointer'); + $(window).trigger('action:settings.sorted-list.loaded', { element: $list.get(0) }); }, }; diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js index 2a9517ae4d..e49a4755ae 100644 --- a/src/controllers/admin/settings.js +++ b/src/controllers/admin/settings.js @@ -17,7 +17,6 @@ settingsController.get = async function (req, res) { res.render('admin/settings/' + term); }; - settingsController.email = async (req, res) => { const emails = await emailer.getTemplates(meta.config); diff --git a/src/meta/settings.js b/src/meta/settings.js index a80c948899..859db5c267 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -47,6 +47,8 @@ Settings.set = async function (hash, values, quiet) { } } + ({ plugin: hash, settings: values, quiet } = await plugins.fireHook('filter:settings.set', { plugin: hash, settings: values, quiet })); + if (sortedLists.length) { await db.delete('settings:' + hash + ':sorted-lists'); await db.setAdd('settings:' + hash + ':sorted-lists', sortedLists); diff --git a/src/plugins/index.js b/src/plugins/index.js index 1d84174c12..342fdb2f85 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -10,6 +10,8 @@ const request = require('request-promise-native'); const user = require('../user'); const posts = require('../posts'); +const utils = require('../utils'); + const { pluginNamePattern, themeNamePattern, paths } = require('../constants'); var app; @@ -121,6 +123,7 @@ Plugins.reload = async function () { console.log(''); } + // Possibly put these in a different file... Plugins.registerHook('core', { hook: 'filter:parse.post', method: async (data) => { @@ -147,6 +150,26 @@ Plugins.reload = async function () { }, }); + Plugins.registerHook('core', { + hook: 'filter:settings.set', + method: async ({ plugin, settings, quiet }) => { + if (plugin === 'core.api' && Array.isArray(settings.tokens)) { + // Generate tokens if not present already + settings.tokens.forEach((set) => { + if (set.token === '') { + set.token = utils.generateUUID(); + } + + if (isNaN(parseInt(set.uid, 10))) { + set.uid = 0; + } + }); + } + + return { plugin, settings, quiet }; + }, + }); + // Lower priority runs earlier Object.keys(Plugins.loadedHooks).forEach(function (hook) { Plugins.loadedHooks[hook].sort((a, b) => a.priority - b.priority); diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 34315af324..97a6325bc2 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -6,7 +6,7 @@ var passportLocal = require('passport-local').Strategy; const BearerStrategy = require('passport-http-bearer').Strategy; var winston = require('winston'); -const db = require('../database'); +const meta = require('../meta'); var controllers = require('../controllers'); var helpers = require('../controllers/helpers'); var plugins = require('../plugins'); @@ -51,9 +51,16 @@ Auth.getLoginStrategies = function () { }; Auth.verifyToken = async function (token, done) { - const uid = await db.sortedSetScore('apiTokens', token); + let { tokens } = await meta.settings.get('core.api'); + tokens = tokens.reduce((memo, cur) => { + memo[cur.token] = cur.uid; + return memo; + }, {}); - if (uid !== null) { + const uid = tokens[token]; + + if (uid !== undefined) { + console.log('uid is', uid); if (parseInt(uid, 10) > 0) { done(null, { uid: uid, diff --git a/src/views/admin/partials/api/sorted-list/form.tpl b/src/views/admin/partials/api/sorted-list/form.tpl new file mode 100644 index 0000000000..317cef089d --- /dev/null +++ b/src/views/admin/partials/api/sorted-list/form.tpl @@ -0,0 +1,14 @@ +
+ +
+ + +

+ [[admin/settings/api:uid-help-text]] +

+
+
+ + +
+
\ No newline at end of file diff --git a/src/views/admin/partials/api/sorted-list/item.tpl b/src/views/admin/partials/api/sorted-list/item.tpl new file mode 100644 index 0000000000..5615bd2156 --- /dev/null +++ b/src/views/admin/partials/api/sorted-list/item.tpl @@ -0,0 +1,13 @@ +
  • +
    +
    + {{{ if uid }}}uid {uid}{{{ else }}}master{{{ end }}} + {{{ if token }}}{{{ else }}}Token will be generated once form is saved{{{ end }}}
    + {description} +
    +
    + + +
    +
    +
  • \ No newline at end of file diff --git a/src/views/admin/settings/api.tpl b/src/views/admin/settings/api.tpl new file mode 100644 index 0000000000..783d53b6bd --- /dev/null +++ b/src/views/admin/settings/api.tpl @@ -0,0 +1,25 @@ + + +
    +
    +
    [[admin/settings/api:tokens]]
    +
    +

    [[admin/settings/api:lead-text]]

    +

    [[admin/settings/api:intro]]

    +

    + + + [[admin/settings/api:docs]] + +

    + +
    + +
      + +
      +
      +
      +
      + + \ No newline at end of file