mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-27 09:06:15 +01:00
feat: management of API tokens via ACP
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
"settings/pagination": "Pagination",
|
"settings/pagination": "Pagination",
|
||||||
"settings/tags": "Tags",
|
"settings/tags": "Tags",
|
||||||
"settings/notifications": "Notifications",
|
"settings/notifications": "Notifications",
|
||||||
|
"settings/api": "API Access",
|
||||||
"settings/sounds": "Sounds",
|
"settings/sounds": "Sounds",
|
||||||
"settings/social": "Social",
|
"settings/social": "Social",
|
||||||
"settings/cookies": "Cookies",
|
"settings/cookies": "Cookies",
|
||||||
|
|||||||
10
public/language/en-GB/admin/settings/api.json
Normal file
10
public/language/en-GB/admin/settings/api.json
Normal file
@@ -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 <code>0</code>, it will be considered a <em>master</em> token, which can assume the identity of other users based on the <code>_uid</code> parameter",
|
||||||
|
"description": "Description"
|
||||||
|
}
|
||||||
36
public/src/admin/settings/api.js
Normal file
36
public/src/admin/settings/api.js
Normal file
@@ -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;
|
||||||
|
});
|
||||||
@@ -60,6 +60,7 @@ define('settings/sorted-list', ['benchpress', 'jqueryui'], function (benchpress)
|
|||||||
});
|
});
|
||||||
|
|
||||||
$list.sortable().addClass('pointer');
|
$list.sortable().addClass('pointer');
|
||||||
|
$(window).trigger('action:settings.sorted-list.loaded', { element: $list.get(0) });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ settingsController.get = async function (req, res) {
|
|||||||
res.render('admin/settings/' + term);
|
res.render('admin/settings/' + term);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
settingsController.email = async (req, res) => {
|
settingsController.email = async (req, res) => {
|
||||||
const emails = await emailer.getTemplates(meta.config);
|
const emails = await emailer.getTemplates(meta.config);
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
if (sortedLists.length) {
|
||||||
await db.delete('settings:' + hash + ':sorted-lists');
|
await db.delete('settings:' + hash + ':sorted-lists');
|
||||||
await db.setAdd('settings:' + hash + ':sorted-lists', sortedLists);
|
await db.setAdd('settings:' + hash + ':sorted-lists', sortedLists);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const request = require('request-promise-native');
|
|||||||
|
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
|
const utils = require('../utils');
|
||||||
|
|
||||||
const { pluginNamePattern, themeNamePattern, paths } = require('../constants');
|
const { pluginNamePattern, themeNamePattern, paths } = require('../constants');
|
||||||
|
|
||||||
var app;
|
var app;
|
||||||
@@ -121,6 +123,7 @@ Plugins.reload = async function () {
|
|||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Possibly put these in a different file...
|
||||||
Plugins.registerHook('core', {
|
Plugins.registerHook('core', {
|
||||||
hook: 'filter:parse.post',
|
hook: 'filter:parse.post',
|
||||||
method: async (data) => {
|
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
|
// Lower priority runs earlier
|
||||||
Object.keys(Plugins.loadedHooks).forEach(function (hook) {
|
Object.keys(Plugins.loadedHooks).forEach(function (hook) {
|
||||||
Plugins.loadedHooks[hook].sort((a, b) => a.priority - b.priority);
|
Plugins.loadedHooks[hook].sort((a, b) => a.priority - b.priority);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ var passportLocal = require('passport-local').Strategy;
|
|||||||
const BearerStrategy = require('passport-http-bearer').Strategy;
|
const BearerStrategy = require('passport-http-bearer').Strategy;
|
||||||
var winston = require('winston');
|
var winston = require('winston');
|
||||||
|
|
||||||
const db = require('../database');
|
const meta = require('../meta');
|
||||||
var controllers = require('../controllers');
|
var controllers = require('../controllers');
|
||||||
var helpers = require('../controllers/helpers');
|
var helpers = require('../controllers/helpers');
|
||||||
var plugins = require('../plugins');
|
var plugins = require('../plugins');
|
||||||
@@ -51,9 +51,16 @@ Auth.getLoginStrategies = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Auth.verifyToken = async function (token, done) {
|
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) {
|
if (parseInt(uid, 10) > 0) {
|
||||||
done(null, {
|
done(null, {
|
||||||
uid: uid,
|
uid: uid,
|
||||||
|
|||||||
14
src/views/admin/partials/api/sorted-list/form.tpl
Normal file
14
src/views/admin/partials/api/sorted-list/form.tpl
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<form>
|
||||||
|
<input type="hidden" name="token">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="uid">[[admin/settings/api:uid]]</label>
|
||||||
|
<input type="number" name="uid" class="form-control" placeholder="1" min="0" />
|
||||||
|
<p class="help-text">
|
||||||
|
[[admin/settings/api:uid-help-text]]
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">[[admin/settings/api:description]]</label>
|
||||||
|
<input type="text" name="description" class="form-control" placeholder="Description" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
13
src/views/admin/partials/api/sorted-list/item.tpl
Normal file
13
src/views/admin/partials/api/sorted-list/item.tpl
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<li data-type="item" class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-9">
|
||||||
|
<span class="label label-primary">{{{ if uid }}}uid {uid}{{{ else }}}master{{{ end }}}</span>
|
||||||
|
{{{ if token }}}<input type="text" readonly="readonly" value="{token}" size="32" />{{{ else }}}<em class="text-warning">Token will be generated once form is saved</em>{{{ end }}}<br />
|
||||||
|
<small>{description}</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3 text-right">
|
||||||
|
<button type="button" data-type="edit" class="btn btn-info">Edit</button>
|
||||||
|
<button type="button" data-type="remove" class="btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
25
src/views/admin/settings/api.tpl
Normal file
25
src/views/admin/settings/api.tpl
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!-- IMPORT admin/partials/settings/header.tpl -->
|
||||||
|
|
||||||
|
<form role="form" class="core-api-settings">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/api:tokens]]</div>
|
||||||
|
<div class="col-sm-10 col-xs-12">
|
||||||
|
<p class="lead">[[admin/settings/api:lead-text]]</p>
|
||||||
|
<p>[[admin/settings/api:intro]]</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://docs.nodebb.org/api">
|
||||||
|
<i class="fa fa-external-link"></i>
|
||||||
|
[[admin/settings/api:docs]]
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="form-group" data-type="sorted-list" data-sorted-list="tokens" data-item-template="admin/partials/api/sorted-list/item" data-form-template="admin/partials/api/sorted-list/form">
|
||||||
|
<input hidden="text" name="tokens">
|
||||||
|
<ul data-type="list" class="list-group"></ul>
|
||||||
|
<button type="button" data-type="add" class="btn btn-info">Create Token</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- IMPORT admin/partials/settings/footer.tpl -->
|
||||||
Reference in New Issue
Block a user