parent
ed9d2c64f1
commit
c35d0f5a3e
@ -0,0 +1,60 @@
|
||||
// admin/content-report.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const DTP_COMPONENT_NAME = 'admin:content-report';
|
||||
|
||||
const express = require('express');
|
||||
// const multer = require('multer');
|
||||
|
||||
const { /*SiteError,*/ SiteController } = require('../../../lib/site-lib');
|
||||
|
||||
class CoreNodeController extends SiteController {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, DTP_COMPONENT_NAME);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
// const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/upload` });
|
||||
|
||||
const router = express.Router();
|
||||
router.use(async (req, res, next) => {
|
||||
res.locals.currentView = 'admin';
|
||||
res.locals.adminView = 'core-node';
|
||||
return next();
|
||||
});
|
||||
|
||||
router.post('/connect', this.postCoreNodeConnect.bind(this));
|
||||
router.get('/connect', this.getCoreNodeConnectForm.bind(this));
|
||||
|
||||
router.get('/', this.getIndex.bind(this));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async postCoreNodeConnect (req, res, next) {
|
||||
// const { coreNode: coreNodeService } = this.dtp.services;
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
this.log.error('failed to create Core Node connection request', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getCoreNodeConnectForm (req, res) {
|
||||
res.render('admin/core-node/connect');
|
||||
}
|
||||
|
||||
async getIndex (req, res) {
|
||||
res.render('admin/core-node/index');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async (dtp) => {
|
||||
let controller = new CoreNodeController(dtp);
|
||||
return controller;
|
||||
};
|
@ -0,0 +1,64 @@
|
||||
// hive.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const DTP_COMPONENT_NAME = 'hive';
|
||||
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
|
||||
const { SiteController } = require('../../lib/site-lib');
|
||||
|
||||
class HiveController extends SiteController {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, DTP_COMPONENT_NAME);
|
||||
this.services = [ ];
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
const router = express.Router();
|
||||
this.dtp.app.use('/hive', router);
|
||||
|
||||
router.use(
|
||||
async (req, res, next) => {
|
||||
res.locals.currentView = 'hive';
|
||||
res.locals.hiveView = 'home';
|
||||
|
||||
/*
|
||||
* TODO: H1V3 authentication before processing request (HTTP Bearer token)
|
||||
*/
|
||||
|
||||
return next();
|
||||
},
|
||||
);
|
||||
|
||||
router.use('/kaleidoscope',await this.loadChild(path.join(__dirname, 'hive', 'kaleidoscope')));
|
||||
this.services.push({ name: 'kaleidoscope', url: '/hive/kaleidoscope' });
|
||||
|
||||
router.get('/', this.getHiveRoot.bind(this));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async getHiveRoot (req, res) {
|
||||
res.status(200).json({
|
||||
component: DTP_COMPONENT_NAME,
|
||||
host: this.dtp.pkg.name,
|
||||
description: this.dtp.pkg.description,
|
||||
version: this.dtp.pkg.version,
|
||||
services: this.services,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
slug: 'hive',
|
||||
name: 'hive',
|
||||
create: async (dtp) => {
|
||||
let controller = new HiveController(dtp);
|
||||
return controller;
|
||||
},
|
||||
};
|
@ -0,0 +1,72 @@
|
||||
// hive/kaleidoscope.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const DTP_COMPONENT_NAME = 'hive:kaleidoscope';
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const { SiteController } = require('../../../lib/site-lib');
|
||||
|
||||
class HostController extends SiteController {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, DTP_COMPONENT_NAME);
|
||||
|
||||
this.methods = [
|
||||
{
|
||||
name: 'postEvent',
|
||||
url: '/kaleidoscope/event',
|
||||
method: 'POST',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
const router = express.Router();
|
||||
router.use(async (req, res, next) => {
|
||||
res.locals.currentView = 'hive';
|
||||
res.locals.hiveView = 'kaleidoscope';
|
||||
return next();
|
||||
});
|
||||
|
||||
router.post('/core-node/connect', this.postCoreNodeConnect.bind(this));
|
||||
router.post('/event', this.postEvent.bind(this));
|
||||
|
||||
router.get('/', this.getKaleidoscopeRoot.bind(this));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async postCoreNodeConnect (req, res, next) {
|
||||
const { coreNode: coreNodeService } = this.dtp.services;
|
||||
try {
|
||||
await coreNodeService.connect(req.body);
|
||||
} catch (error) {
|
||||
this.log.error('failed to create Core Node connection', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async postEvent (req, res) {
|
||||
this.log.debug('kaleidoscope event received', { event: req.body.event });
|
||||
this.emit('kaleidoscope:event', req, res);
|
||||
res.status(200).json({ success: true });
|
||||
}
|
||||
|
||||
async getKaleidoscopeRoot (req, res) {
|
||||
res.status(200).json({
|
||||
component: DTP_COMPONENT_NAME,
|
||||
version: this.dtp.pkg.version,
|
||||
services: this.services,
|
||||
methods: this.methods,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async (dtp) => {
|
||||
let controller = new HostController(dtp);
|
||||
return controller;
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
// core-node-request.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
/*
|
||||
* Used for authenticating responses received and gathering performance and use
|
||||
* metrics for communications with Cores.
|
||||
*
|
||||
* When a request is created, an authentication token is generated and
|
||||
* information about the request is stored. This also provides the request ID.
|
||||
*
|
||||
* When a resonse is received for a request, this record is fetched. The token
|
||||
* claimed status and value are checked. Information about the response is
|
||||
* recorded, and request execution time information is recorded.
|
||||
*/
|
||||
|
||||
const CoreNodeRequestSchema = new Schema({
|
||||
created: { type: Date, default: Date.now, required: true, index: 1 },
|
||||
core: { type: Schema.ObjectId, required: true, ref: 'CoreNode' },
|
||||
token: {
|
||||
value: { type: String, required: true },
|
||||
claimed: { type: Boolean, default: false, required: true },
|
||||
},
|
||||
url: { type: String },
|
||||
response: {
|
||||
received: { type: Date },
|
||||
elapsed: { type: Number },
|
||||
isError: { type: Boolean, default: false },
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('CoreNodeRequest', CoreNodeRequestSchema);
|
@ -0,0 +1,18 @@
|
||||
// core-node.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const CoreNodeSchema = new Schema({
|
||||
created: { type: Date, default: Date.now, required: true, index: 1 },
|
||||
address: {
|
||||
host: { type: String, required: true },
|
||||
port: { type: Number, min: 1, max: 65535, required: true },
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('CoreNode', CoreNodeSchema);
|
@ -0,0 +1,97 @@
|
||||
// core-node.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const uuidv4 = require('uuid').v4;
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const fetch = require('node-fetch'); // jshint ignore:line
|
||||
|
||||
const CoreNode = mongoose.model('CoreNode');
|
||||
const CoreNodeRequest = mongoose.model('CoreNodeRequest');
|
||||
|
||||
const { SiteService, SiteError } = require('../../lib/site-lib');
|
||||
|
||||
class CoreNodeService extends SiteService {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, module.exports);
|
||||
}
|
||||
|
||||
async create (coreDefinition) {
|
||||
const core = new CoreNode();
|
||||
core.created = new Date();
|
||||
|
||||
core.address = { };
|
||||
|
||||
if (!coreDefinition.host) {
|
||||
throw new SiteError(406, 'Must provide Core Node host address');
|
||||
}
|
||||
core.address.host = coreDefinition.host.trim();
|
||||
|
||||
if (!coreDefinition.port) {
|
||||
throw new SiteError(406, 'Must provide Core Node TCP port number');
|
||||
}
|
||||
|
||||
coreDefinition.port = parseInt(coreDefinition.port, 10);
|
||||
if (coreDefinition.port < 1 || coreDefinition.port > 65535) {
|
||||
throw new SiteError(406, 'Core Node port number out of range');
|
||||
}
|
||||
|
||||
await core.save();
|
||||
|
||||
return core.toObject();
|
||||
}
|
||||
|
||||
async broadcast (request) {
|
||||
const results = [ ];
|
||||
await CoreNode
|
||||
.find()
|
||||
.cursor()
|
||||
.eachAsync(async (core) => {
|
||||
try {
|
||||
const response = await this.sendRequest(core, request);
|
||||
results.push({ coreId: core._id, request, response });
|
||||
} catch (error) {
|
||||
this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error });
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
async sendRequest (core, request) {
|
||||
const requestUrl = `https://${core.address.host}:${core.address.port}${request.url}`;
|
||||
|
||||
const req = new CoreNodeRequest();
|
||||
req.created = new Date();
|
||||
req.core = core._id;
|
||||
req.token = {
|
||||
value: uuidv4(),
|
||||
claimed: false,
|
||||
};
|
||||
req.url = request.url;
|
||||
await req.save();
|
||||
|
||||
try {
|
||||
const response = await fetch(requestUrl, {
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
});
|
||||
const json = await response.json();
|
||||
return { request: req.toObject(), response: json };
|
||||
} catch (error) {
|
||||
this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error });
|
||||
throw error;
|
||||
}
|
||||
|
||||
return req.toObject();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
slug: 'core-node',
|
||||
name: 'coreNode',
|
||||
create: (dtp) => { return new CoreNodeService(dtp); },
|
||||
};
|
@ -0,0 +1,191 @@
|
||||
// oauth2.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const passport = require('passport');
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const uuidv4 = require('uuid').v4;
|
||||
const oauth2orize = require('oauth2orize');
|
||||
|
||||
const { SiteService/*, SiteError*/ } = require('../../lib/site-lib');
|
||||
|
||||
class OAuth2Service extends SiteService {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, module.exports);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
this.models = { };
|
||||
|
||||
/*
|
||||
* OAuth2Client Model
|
||||
*/
|
||||
const ClientSchema = new Schema({
|
||||
created: { type: Date, default: Date.now, required: true },
|
||||
updated: { type: Date, default: Date.now, required: true },
|
||||
secret: { type: String, required: true },
|
||||
redirectURI: { type: String, required: true },
|
||||
});
|
||||
this.log.info('registering OAuth2Client model');
|
||||
this.models.Client = mongoose.model('OAuth2Client', ClientSchema);
|
||||
|
||||
/*
|
||||
* OAuth2AuthorizationCode model
|
||||
*/
|
||||
const AuthorizationCodeSchema = new Schema({
|
||||
code: { type: String, required: true, index: 1 },
|
||||
clientId: { type: Schema.ObjectId, required: true, index: 1 },
|
||||
redirectURI: { type: String, required: true },
|
||||
user: { type: Schema.ObjectId, required: true, index: 1 },
|
||||
scope: { type: [String], required: true },
|
||||
});
|
||||
this.log.info('registering OAuth2AuthorizationCode model');
|
||||
this.models.AuthorizationCode = mongoose.model('OAuth2AuthorizationCode', AuthorizationCodeSchema);
|
||||
|
||||
/*
|
||||
* OAuth2AccessToken model
|
||||
*/
|
||||
const AccessTokenSchema = new Schema({
|
||||
token: { type: String, required: true, unique: true, index: 1 },
|
||||
user: { type: Schema.ObjectId, required: true, index: 1 },
|
||||
clientId: { type: Schema.ObjectId, required: true, index: 1 },
|
||||
scope: { type: [String], required: true },
|
||||
});
|
||||
this.log.info('registering OAuth2AccessToken model');
|
||||
this.models.AccessToken = mongoose.model('OAuth2AccessToken', AccessTokenSchema);
|
||||
|
||||
/*
|
||||
* Create OAuth2 server instance
|
||||
*/
|
||||
const options = { };
|
||||
this.log.info('creating OAuth2 server instance', { options });
|
||||
this.server = oauth2orize.createServer(options);
|
||||
this.server.grant(oauth2orize.grant.code(this.processGrant.bind(this)));
|
||||
this.server.exchange(oauth2orize.exchange.code(this.processExchange.bind(this)));
|
||||
|
||||
/*
|
||||
* Register client serialization callbacks
|
||||
*/
|
||||
this.log.info('registering OAuth2 client serialization routines');
|
||||
this.server.serializeClient(this.serializeClient.bind(this));
|
||||
this.server.deserializeClient(this.deserializeClient.bind(this));
|
||||
}
|
||||
|
||||
async serializeClient (client, done) {
|
||||
return done(null, client.id);
|
||||
}
|
||||
|
||||
async deserializeClient (clientId, done) {
|
||||
try {
|
||||
const client = await this.models.Client.findOne({ _id: clientId }).lean();
|
||||
return done(null, client);
|
||||
} catch (error) {
|
||||
this.log.error('failed to deserialize OAuth2 client', { clientId, error });
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
|
||||
attachRoutes (app) {
|
||||
const { session: sessionService } = this.dtp.services;
|
||||
|
||||
const requireLogin = sessionService.authCheckMiddleware({ requireLogin: true });
|
||||
|
||||
app.get(
|
||||
'/dialog/authorize',
|
||||
requireLogin,
|
||||
this.server.authorize(this.processAuthorize.bind(this)),
|
||||
this.renderAuthorizeDialog.bind(this),
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/dialog/authorize/decision',
|
||||
requireLogin,
|
||||
this.server.decision(),
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/token',
|
||||
passport.authenticate(['basic', 'oauth2-client-password'], { session: false }),
|
||||
this.server.token(),
|
||||
this.server.errorHandler(),
|
||||
);
|
||||
}
|
||||
|
||||
async renderAuthorizeDialog (req, res) {
|
||||
res.locals.transactionID = req.oauth2.transactionID;
|
||||
res.locals.client = req.oauth2.client;
|
||||
res.render('oauth2/authorize-dialog');
|
||||
}
|
||||
|
||||
async processAuthorize (clientID, redirectURI, done) {
|
||||
try {
|
||||
const client = await this.models.Clients.findOne({ clientID });
|
||||
if (!client) {
|
||||
return done(null, false);
|
||||
}
|
||||
if (client.redirectUri !== redirectURI) {
|
||||
return done(null, false);
|
||||
}
|
||||
return done(null, client, client.redirectURI);
|
||||
} catch (error) {
|
||||
this.log.error('failed to process OAuth2 authorize', { error });
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
|
||||
async processGrant (client, redirectURI, user, ares, done) {
|
||||
try {
|
||||
var code = uuidv4();
|
||||
var ac = new this.models.AuthorizationCode({
|
||||
code,
|
||||
clientId: client.id,
|
||||
redirectURI,
|
||||
user: user.id,
|
||||
scope: ares.scope,
|
||||
});
|
||||
await ac.save();
|
||||
return done(null, code);
|
||||
} catch (error) {
|
||||
this.log.error('failed to process OAuth2 grant', { error });
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
|
||||
async processExchange (client, code, redirectURI, done) {
|
||||
try {
|
||||
const ac = await this.models.AuthorizationCode.findOne({ code });
|
||||
if (client.id !== ac.clientId) {
|
||||
return done(null, false);
|
||||
}
|
||||
if (redirectURI !== ac.redirectUri) {
|
||||
return done(null, false);
|
||||
}
|
||||
|
||||
var token = uuidv4();
|
||||
var at = new this.models.AccessToken({
|
||||
token,
|
||||
user: ac.userId,
|
||||
clientId: ac.clientId,
|
||||
scope: ac.scope,
|
||||
});
|
||||
await at.save();
|
||||
|
||||
return done(null, token);
|
||||
} catch (error) {
|
||||
this.log.error('failed to process OAuth2 exchange', { error });
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
slug: 'oauth2',
|
||||
name: 'oauth2',
|
||||
create: (dtp) => { return new OAuth2Service(dtp); },
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
form(method="POST", action="/admin/core-node/connect").uk-form
|
||||
.uk-card.uk-card-default.uk-card-small
|
||||
.uk-card-header
|
||||
h1.uk-card-title Connect to New Core
|
||||
|
||||
.uk-card-body
|
||||
.uk-margin
|
||||
label(for="host").uk-form-label Address
|
||||
input(id="host", name="host", placeholder="Enter host name or address", required).uk-input
|
||||
.uk-margin
|
||||
label(for="port").uk-form-label Port Number
|
||||
input(id="port", name="port", min="1", max="65535", step="1", value="4200", required).uk-input
|
||||
|
||||
.uk-card-footer
|
||||
button(type="submit").uk-button.uk-button-primary Send Request
|
@ -0,0 +1,14 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
h1 Core Nodes
|
||||
a(href="/admin/core-node/connect").uk-button.uk-button-primary Connect Core
|
||||
|
||||
p You can register with one or more Core nodes to exchange information with those nodes.
|
||||
|
||||
if Array.isArray(coreNodes) && (coreNodes.length > 0)
|
||||
ul.uk-list
|
||||
each node in coreNodes
|
||||
pre= JSON.stringify(node, null, 2)
|
||||
else
|
||||
p There are no registered core nodes.
|
Loading…
Reference in new issue