[BREAKING] Allow possibility of disabling Scalar upstream

This is a breaking change because of the structure change for upstreams. Instead of being an object, it is now a list. Existing configurations are not guaranteed to work.

Adds #108 and starts work on #22 (upstream config).
This commit is contained in:
turt2live 2017-09-11 20:50:43 -06:00
parent 1138dc3c0a
commit 02c981b070
16 changed files with 196 additions and 55 deletions

View file

@ -22,4 +22,5 @@ demobot:
# Upstream configuration. This should almost never change.
upstreams:
vector: "https://scalar.vector.im/api"
- name: vector
url: "https://scalar.vector.im/api"

View file

@ -32,7 +32,11 @@ class DimensionApi {
var factory = IntegrationImpl.getFactory(integrationConfig);
if (!factory) throw new Error("Missing config factory for " + integrationConfig.name);
return factory(this._db, integrationConfig, roomId, scalarToken);
try {
return factory(this._db, integrationConfig, roomId, scalarToken);
} catch (err) {
throw new Error("Error using factory for " + integrationConfig.name + ". Please either fix the integration settings or disable the integration.", err);
}
}
_getIntegrations(req, res) {
@ -49,21 +53,34 @@ class DimensionApi {
var integrations = _.map(Integrations.all, i => JSON.parse(JSON.stringify(i))); // clone
var promises = [];
var remove = [];
_.forEach(integrations, integration => {
promises.push(this._getIntegration(integration, roomId, scalarToken).then(builtIntegration => {
return builtIntegration.getState().then(state => {
var keys = _.keys(state);
for (var key of keys) {
integration[key] = state[key];
}
try {
promises.push(this._getIntegration(integration, roomId, scalarToken).then(builtIntegration => {
return builtIntegration.getState().then(state => {
var keys = _.keys(state);
for (var key of keys) {
integration[key] = state[key];
}
return builtIntegration.getUserId();
}).then(userId => {
integration.userId = userId;
});
}));
return builtIntegration.getUserId();
}).then(userId => {
integration.userId = userId;
});
}));
} catch (err) {
remove.push(integration);
log.error("DimensionApi", err);
}
});
for (var toRemove of remove) {
var idx = integrations.indexOf(toRemove);
if (idx === -1) continue;
log.warn("DimensionApi", "Disabling integration " + toRemove.name +" due to an error encountered in setup");
integrations.splice(idx, 1);
}
Promise.all(promises).then(() => res.send(_.map(integrations, integration => {
// Remove sensitive material
integration.upstream = undefined;

View file

@ -4,6 +4,7 @@ var ScalarClient = require("./scalar/ScalarClient.js");
var _ = require("lodash");
var log = require("./util/LogService");
var Promise = require("bluebird");
var UpstreamConfiguration = require("./UpstreamConfiguration");
/**
* API handler for the Scalar API, as required by Riot
@ -33,7 +34,11 @@ class ScalarApi {
if (!token) res.sendStatus(400);
else this._db.checkToken(token).then(() => {
res.sendStatus(200);
}).catch(() => res.sendStatus(401));
}).catch(e => {
res.sendStatus(401);
log.warn("ScalarApi", "Failed to authenticate token");
log.verbose("ScalarApi", e);
});
}
_scalarRegister(req, res) {
@ -51,6 +56,9 @@ class ScalarApi {
client.getSelfMxid().then(mxid => {
userId = mxid;
if (!mxid) throw new Error("Token does not resolve to a matrix user");
// TODO: Make this part more generic for other upstreams (#22)
if (!UpstreamConfiguration.hasUpstream("vector")) return Promise.resolve(null);
return ScalarClient.register(tokenInfo);
}).then(upstreamToken => {
return this._db.createToken(userId, tokenInfo, scalarToken, upstreamToken);
@ -64,4 +72,4 @@ class ScalarApi {
}
}
module.exports = new ScalarApi();
module.exports = new ScalarApi();

View file

@ -0,0 +1,50 @@
var LogService = require("./util/LogService");
var _ = require("lodash");
var config = require("config");
/**
* Handles all upstream configuration information, such as URLs, tokens, and whether or not they are enabled.
*/
class UpstreamConfiguration {
/**
* Creates a new upstream configuration handler
*/
constructor() {
this._upstreams = {};
this._loadUpstreams();
}
_loadUpstreams() {
for (var upstream of config.upstreams) {
var upstreamConfig = upstream;
if (this._upstreams[upstream.name]) {
LogService.warn("UpstreamConfiguration", "Duplicate upstream " + upstream.name +" - skipping");
continue;
}
this._upstreams[upstream.name] = upstreamConfig;
LogService.info("UpstreamConfiguration", "Loaded upstream '" + upstream.name + "' as: " + JSON.stringify(upstreamConfig));
}
}
/**
* Checks if a particular upstream exists
* @param {string} name the name of the upstream
* @returns {boolean} true if it is enabled and exists
*/
hasUpstream(name) {
return !!this._upstreams[name];
}
/**
* Gets an upstream's configuration
* @param {string} name the upstream name
* @returns {{url:string}} the upstream configuration
*/
getUpstream(name) {
return _.clone(this._upstreams[name]);
}
}
module.exports = new UpstreamConfiguration();

View file

@ -8,6 +8,13 @@ var IntegrationStub = require("../generic_types/IntegrationStub");
* @param {string} scalarToken the scalar token
* @returns {Promise<*>} resolves to the configured integration
*/
module.exports = (db, integrationConfig, roomId, scalarToken) => {
var factory = (db, integrationConfig, roomId, scalarToken) => {
factory.validateConfig(integrationConfig);
return Promise.resolve(new IntegrationStub(integrationConfig));
};
};
factory.validateConfig = (integrationConfig) => {
// Nothing to do
};
module.exports = factory;

View file

@ -19,7 +19,7 @@ class IRCApi {
* @param {DimensionStore} db the store to use
*/
bootstrap(app, db) {
if (!Integrations.byType["bridge"]["irc"]) {
if (!Integrations.byType["bridge"] || !Integrations.byType["bridge"]["irc"]) {
log.info("IRCApi", "IRC Bridge not enabled - not setting up the API");
return;
} else log.info("IRCApi", "Setting up IRC API");

View file

@ -1,12 +1,20 @@
var IRCBridge = require("./IRCBridge");
var VectorIrcBackbone = require("./VectorIrcBackbone");
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
module.exports = (db, integrationConfig, roomId, scalarToken) => {
if (integrationConfig.upstream) {
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
var backbone = new VectorIrcBackbone(roomId, upstreamToken);
return new IRCBridge(integrationConfig, backbone);
});
} else throw new Error("Unsupported config");
};
var factory = (db, integrationConfig, roomId, scalarToken) => {
factory.validateConfig(integrationConfig);
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
var backbone = new VectorIrcBackbone(roomId, upstreamToken);
return new IRCBridge(integrationConfig, backbone);
});
};
factory.validateConfig = (integrationConfig) => {
if (!integrationConfig.upstream) throw new Error("Unsupported configuration");
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
};
module.exports = factory;

View file

@ -1,12 +1,20 @@
var RSSBot = require("./RSSBot");
var VectorRssBackbone = require("./VectorRssBackbone");
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
module.exports = (db, integrationConfig, roomId, scalarToken) => {
if (integrationConfig.upstream) {
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
var backbone = new VectorRssBackbone(roomId, upstreamToken);
return new RSSBot(integrationConfig, backbone);
});
} else throw new Error("Unsupported config");
};
var factory = (db, integrationConfig, roomId, scalarToken) => {
factory.validateConfig(integrationConfig);
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
var backbone = new VectorRssBackbone(roomId, upstreamToken);
return new RSSBot(integrationConfig, backbone);
});
};
factory.validateConfig = (integrationConfig) => {
if (!integrationConfig.upstream) throw new Error("Unsupported configuration");
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
};
module.exports = factory;

View file

@ -1,10 +1,12 @@
var SimpleBot = require("./SimpleBot");
var VectorSimpleBackbone = require("./VectorSimpleBackbone");
var HostedSimpleBackbone = require("./HostedSimpleBackbone");
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
var factory = (db, integrationConfig, roomId, scalarToken) => {
factory.validateConfig(integrationConfig);
module.exports = (db, integrationConfig, roomId, scalarToken) => {
if (integrationConfig.upstream) {
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
var backbone = new VectorSimpleBackbone(integrationConfig, upstreamToken);
return new SimpleBot(integrationConfig, backbone);
@ -12,5 +14,14 @@ module.exports = (db, integrationConfig, roomId, scalarToken) => {
} else if (integrationConfig.hosted) {
var backbone = new HostedSimpleBackbone(integrationConfig);
return Promise.resolve(new SimpleBot(integrationConfig, backbone));
} else throw new Error("Unsupported config");
};
}
};
factory.validateConfig = (integrationConfig) => {
if (integrationConfig.upstream) {
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
} else if (!integrationConfig.hosted) throw new Error("Unsupported configuration");
};
module.exports = factory;

View file

@ -1,6 +1,13 @@
var SimpleWidget = require("./SimpleWidget");
var Promise = require("bluebird");
module.exports = (db, integrationConfig, roomId, scalarToken) => {
var factory = (db, integrationConfig, roomId, scalarToken) => {
factory.validateConfig(integrationConfig);
return Promise.resolve(new SimpleWidget(integrationConfig, roomId));
};
};
factory.validateConfig = (integrationConfig) => {
// Nothing to do
};
module.exports = factory;

View file

@ -1,12 +1,20 @@
var TravisCiBot = require("./TravisCiBot");
var VectorTravisCiBackbone = require("./VectorTravisCiBackbone");
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
module.exports = (db, integrationConfig, roomId, scalarToken) => {
if (integrationConfig.upstream) {
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
var backbone = new VectorTravisCiBackbone(roomId, upstreamToken);
return new TravisCiBot(integrationConfig, backbone);
});
} else throw new Error("Unsupported config");
};
var factory = (db, integrationConfig, roomId, scalarToken) => {
factory.validateConfig(integrationConfig);
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
var backbone = new VectorTravisCiBackbone(roomId, upstreamToken);
return new TravisCiBot(integrationConfig, backbone);
});
};
factory.validateConfig = (integrationConfig) => {
if (!integrationConfig.upstream) throw new Error("Unsupported configuration");
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
};
module.exports = factory;

View file

@ -3,6 +3,7 @@ var log = require("../util/LogService");
var fs = require("fs");
var path = require("path");
var _ = require("lodash");
var IntegrationImpl = require("./impl");
log.info("Integrations", "Discovering integrations");
@ -43,6 +44,19 @@ for (var key of keys) {
continue;
}
var factory = IntegrationImpl.getFactory(merged);
if (!factory) {
log.warn("Integrations", "Integration " + key + " does not have an associated factory - skipping");
continue;
}
try {
factory.validateConfig(merged);
} catch (err) {
log.error("Integrations", "Error while validating integration " + key + " - skipping");
log.error("Integrations", err);
continue;
}
linear.push(merged);
if (merged['userId'])
byUserId[merged['userId']] = merged;

View file

@ -1,6 +1,7 @@
var request = require('request');
var log = require("../util/LogService");
var config = require("config");
var UpstreamConfiguration = require("../UpstreamConfiguration");
/**
* Represents a scalar client
@ -32,7 +33,7 @@ class ScalarClient {
// TODO: Merge this, VectorScalarClient, and MatrixLiteClient into a base class
_do(method, endpoint, qs = null, body = null) {
// TODO: Generify URL
var url = config.get("upstreams.vector") + endpoint;
var url = UpstreamConfiguration.getUpstream("vector").url + endpoint;
log.verbose("ScalarClient", "Performing request: " + url);

View file

@ -1,6 +1,7 @@
var request = require('request');
var log = require("../util/LogService");
var config = require("config");
var UpstreamConfiguration = require("../UpstreamConfiguration");
/**
* Represents a scalar client for vector.im
@ -234,7 +235,7 @@ class VectorScalarClient {
}
_do(method, endpoint, qs = null, body = null) {
var url = config.get("upstreams.vector") + endpoint;
var url = UpstreamConfiguration.getUpstream("vector").url + endpoint;
log.verbose("VectorScalarClient", "Performing request: " + url);

View file

@ -65,7 +65,7 @@ class DimensionStore {
* @param {string} mxid the matrix user id
* @param {OpenID} openId the open ID
* @param {string} scalarToken the token associated with the user
* @param {string} upstreamToken the upstream scalar token
* @param {String?} upstreamToken the upstream scalar token (optional)
* @returns {Promise<>} resolves when complete
*/
createToken(mxid, openId, scalarToken, upstreamToken) {

View file

@ -29,7 +29,7 @@ module.exports = function (sequelize, DataTypes) {
},
upstreamToken: {
type: DataTypes.STRING,
allowNull: false,
allowNull: true,
field: 'upstreamToken'
},
expires: {