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 @@
+