Respond to /register requests from Riot.

Implements #1
This commit is contained in:
turt2live 2017-05-26 23:08:24 -06:00
parent f031a7833a
commit afeb2c7bfe
11 changed files with 220 additions and 3 deletions

View File

View File

@ -0,0 +1,34 @@
'use strict';
var dbm;
var type;
var seed;
/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
};
exports.up = function (db) {
return db.createTable("tokens", {
id: {type: 'int', primaryKey: true, autoIncrement: true, notNull: true},
matrixUserId: {type: 'string', notNull: true},
matrixServerName: {type: 'string', notNull: true},
matrixAccessToken: {type: 'string', notNull: true},
scalarToken: {type: 'string', notNull: true},
expires: {type: 'timestamp', notNull: true}
});
};
exports.down = function (db) {
return db.dropTable("tokens");
};
exports._meta = {
"version": 1
};

View File

@ -5,7 +5,7 @@
"main": "app.js",
"license": "GPL-3.0",
"scripts": {
"dev": "webpack-dev-server --inline --progress --port 8080",
"dev": "webpack-dev-server --inline --progress --port 8080 --host 0.0.0.0",
"build": "rimraf web-dist && webpack --progress --profile --bail"
},
"repository": {
@ -14,6 +14,7 @@
},
"author": "Travis Ralston",
"dependencies": {
"body-parser": "^1.17.2",
"chalk": "^1.1.3",
"config": "^1.25.1",
"db-migrate": "^0.10.0-beta.20",
@ -21,6 +22,8 @@
"express": "^4.15.2",
"js-yaml": "^3.8.2",
"moment": "^2.18.1",
"random-string": "^0.2.0",
"request": "^2.81.0",
"sequelize": "^3.30.4",
"sqlite3": "^3.1.8",
"winston": "^2.3.1"

View File

@ -2,6 +2,10 @@ var express = require("express");
var config = require("config");
var log = require("./util/LogService");
var DimensionStore = require("./storage/DimensionStore");
var bodyParser = require('body-parser');
var path = require("path");
var MatrixLiteClient = require("./matrix/MatrixLiteClient");
var randomString = require("random-string");
/**
* Primary entry point for Dimension
@ -16,6 +20,27 @@ class Dimension {
this._db = db;
this._app = express();
this._app.use(express.static('web-dist'));
this._app.use(bodyParser.json());
// Register routes for angular app
this._app.get(['/riot/*', '/app/*', '/splash/*'], (req, res) => {
res.sendFile(path.join(__dirname, "..", "web-dist", "index.html"));
});
// Allow CORS
this._app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// Logging incoming requests
this._app.use((req, res, next) => {
log.verbose("Dimension", "Incoming: " + req.method + " " + req.url);
next();
});
this._app.post("/api/v1/scalar/register", this._scalarRegister.bind(this));
}
start() {
@ -23,6 +48,25 @@ class Dimension {
log.info("Dimension", "API and UI listening on " + config.get("web.address") + ":" + config.get("web.port"));
}
_scalarRegister(req, res) {
var tokenInfo = req.body;
if (!tokenInfo || !tokenInfo['access_token'] || !tokenInfo['token_type'] || !tokenInfo['matrix_server_name'] || !tokenInfo['expires_in']) {
res.status(400).send('Missing OpenID');
return;
}
var client = new MatrixLiteClient(tokenInfo);
var scalarToken = randomString({length: 25});
client.getSelfMxid().then(mxid => {
return this._db.createToken(mxid, tokenInfo, scalarToken);
}).then(() => {
res.setHeader("Content-Type", "application/json");
res.send(JSON.stringify({scalar_token: scalarToken}));
}, err => {
throw err;
//res.status(500).send(err.message);
});
}
}
module.exports = Dimension;

11
src/OpenID.js Normal file
View File

@ -0,0 +1,11 @@
/**
* Serves the purpose of being a documentation endpoint
*/
class OpenID {
access_token = "";
token_type = "";
matrix_server_name = "";
expires_in = 0;
}
module.exports = OpenID;

View File

@ -0,0 +1,53 @@
var request = require('request');
var log = require("../util/LogService");
/**
* Represents a lightweight matrix client with minimal functionality
*/
class MatrixLiteClient {
/**
* Creates a new matrix client
* @param {OpenID} openId the open ID to use
*/
constructor(openId) {
this._openId = openId;
}
/**
* Gets the Matrix User ID that owns this open ID
* @return {Promise<string>} resolves to the mxid
*/
getSelfMxid() {
return this._do("GET", "/_matrix/federation/v1/openid/userinfo").then((response, body) => {
var json = JSON.parse(response.body);
return json['sub'];
});
}
_do(method, endpoint, qs = null, body = null) {
var url = "http://" + this._openId.matrix_server_name + endpoint;
log.verbose("MatrixLiteClient", "Performing request: " + url);
if (!qs) qs = {};
qs['access_token'] = this._openId.access_token;
var params = {
url: url,
method: method,
form: body,
qs: qs
};
return new Promise((resolve, reject) => {
request(params, (err, response, body) => {
if (err) {
reject(err);
} else resolve(response, body);
});
});
}
}
module.exports = MatrixLiteClient;

View File

@ -2,6 +2,7 @@ var DBMigrate = require("db-migrate");
var log = require("./../util/LogService");
var Sequelize = require('sequelize');
var dbConfig = require("../../config/database.json");
var moment = require("moment");
/**
* Primary storage for Dimension.
@ -54,9 +55,40 @@ class DimensionStore {
_bindModels() {
// Models
this.__Tokens = this._orm.import(__dirname + "/models/tokens");
// Relationships
}
/**
* Creates a new Scalar token
* @param {string} mxid the matrix user id
* @param {OpenID} openId the open ID
* @param {string} scalarToken the token associated with the user
* @returns {Promise<>} resolves when complete
*/
createToken(mxid, openId, scalarToken) {
return this.__Tokens.create({
matrixUserId: mxid,
matrixServerName: openId.matrix_server_name,
matrixAccessToken: openId.access_token,
scalarToken: scalarToken,
expires: moment().add(openId.expires_in, 'seconds').toDate()
});
}
/**
* Checks to determine if a token is valid or not
* @param {string} scalarToken the scalar token to check
* @returns {Promise<>} resolves if valid, rejected otherwise
*/
checkToken(scalarToken) {
return this.__Tokens.find({where: {scalarToken: scalarToken}}).then(token => {
if (!token) return Promise.reject();
//if (moment().isAfter(moment(token.expires))) return this.__Tokens.destroy({where: {id: token.id}}).then(() => Promise.reject());
return Promise.resolve();
});
}
}
module.exports = DimensionStore;

View File

@ -0,0 +1,40 @@
module.exports = function (sequelize, DataTypes) {
return sequelize.define('tokens', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
field: 'id'
},
matrixUserId: {
type: DataTypes.STRING,
allowNull: false,
field: 'matrixUserId'
},
matrixServerName: {
type: DataTypes.STRING,
allowNull: false,
field: 'matrixServerName'
},
matrixAccessToken: {
type: DataTypes.STRING,
allowNull: false,
field: 'matrixAccessToken'
},
scalarToken: {
type: DataTypes.STRING,
allowNull: false,
field: 'scalarToken'
},
expires: {
type: DataTypes.TIME,
allowNull: false,
field: 'expires'
}
}, {
tableName: 'tokens',
underscored: false,
timestamps: false
});
};

View File

@ -69,7 +69,7 @@ var log = new winston.Logger({
});
function doLog(level, module, messageOrObject) {
if (typeof(messageOrObject) === 'object')
if (typeof(messageOrObject) === 'object' && !(messageOrObject instanceof Error))
messageOrObject = JSON.stringify(messageOrObject);
var message = "[" + module + "] " + messageOrObject;
log.log(level, message);

View File

@ -23,7 +23,7 @@ module.exports = function () {
config.output = {
path: root('web-dist'),
publicPath: isProd ? '/' : 'http://localhost:8080',
publicPath: isProd ? '/' : '/', //http://0.0.0.0:8080',
filename: isProd ? 'js/[name].[hash].js' : 'js/[name].js',
chunkFilename: isProd ? '[id].[hash].chunk.js' : '[id].chunk.js'
};