ltijs
Version:
Turn your application into a fully integratable LTI 1.3 tool or platform.
334 lines (293 loc) • 10.2 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classPrivateFieldGet2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldGet"));
const mongoose = require('mongoose');
mongoose.set('useCreateIndex', true);
const Schema = mongoose.Schema;
const crypto = require('crypto');
const provDatabaseDebug = require('debug')('provider:database');
/**
* @description Collection of static methods to manipulate the database.
*/
class Database {
/**
* @description Mongodb configuration setup
* @param {Object} database - Configuration object
*/
constructor(database) {
_dbConnection.set(this, {
writable: true,
value: {}
});
if (!database.url) throw new Error('Missing database url configuration.'); // Starts database connection
if (database.connection) {
if (!database.connection.useNewUrlParser) database.connection.useNewUrlParser = true;
if (!database.connection.autoReconnect) database.connection.autoReconnect = true;
if (!database.connection.keepAlive) database.connection.keepAlive = true;
if (!database.connection.keepAliveInitialDelay) database.connection.keepAliveInitialDelay = 300000;
} else {
database.connection = {
useNewUrlParser: true,
autoReconnect: true,
keepAlive: true,
keepAliveInitialDelay: 300000,
connectTimeoutMS: 300000
};
}
(0, _classPrivateFieldGet2.default)(this, _dbConnection).url = database.url;
(0, _classPrivateFieldGet2.default)(this, _dbConnection).options = database.connection; // Creating database schemas
const idTokenSchema = new Schema({
iss: String,
issuer_code: String,
user: String,
roles: [String],
userInfo: {
given_name: String,
family_name: String,
name: String,
email: String
},
platformInfo: {
family_code: String,
version: String,
name: String,
description: String
},
endpoint: {
scope: [String],
lineitems: String,
lineitem: String
},
namesRoles: {
context_memberships_url: String,
service_versions: [String]
},
createdAt: {
type: Date,
expires: 3600 * 24,
default: Date.now
}
});
const contextTokenSchema = new Schema({
path: String,
user: String,
context: {
id: String,
label: String,
title: String,
type: Array
},
resource: {
title: String,
id: String
},
// Activity that originated login
custom: JSON,
createdAt: {
type: Date,
expires: 3600 * 24,
default: Date.now
}
});
const platformSchema = new Schema({
platformName: String,
platformUrl: String,
clientId: String,
authEndpoint: String,
accesstokenEndpoint: String,
kid: String,
authConfig: {
method: String,
key: String
}
});
const keySchema = new Schema({
kid: String,
iv: String,
data: String
});
const accessTokenSchema = new Schema({
platformUrl: String,
iv: String,
data: String,
createdAt: {
type: Date,
expires: 3600,
default: Date.now
}
});
const nonceSchema = new Schema({
nonce: String,
createdAt: {
type: Date,
expires: 10,
default: Date.now
}
});
try {
mongoose.model('idtoken', idTokenSchema);
mongoose.model('contexttoken', contextTokenSchema);
mongoose.model('platform', platformSchema);
mongoose.model('privatekey', keySchema);
mongoose.model('publickey', keySchema);
mongoose.model('accesstoken', accessTokenSchema);
mongoose.model('nonce', nonceSchema);
} catch (err) {
provDatabaseDebug('Model already registered. Continuing');
}
this.db = mongoose.connection;
}
/**
* @description Opens connection to database
*/
async setup() {
this.db.on('connected', async () => {
provDatabaseDebug('Database connected');
});
this.db.once('open', async () => {
provDatabaseDebug('Database connection open');
});
this.db.on('error', async () => {
mongoose.disconnect();
});
this.db.on('reconnected', async () => {
provDatabaseDebug('Database reconnected');
});
this.db.on('disconnected', async () => {
provDatabaseDebug('Database disconnected');
provDatabaseDebug('Attempting to reconnect');
setTimeout(async () => {
if (this.db.readyState === 0) {
try {
await mongoose.connect((0, _classPrivateFieldGet2.default)(this, _dbConnection).url, (0, _classPrivateFieldGet2.default)(this, _dbConnection).options);
} catch (err) {
provDatabaseDebug('Error in MongoDb connection: ' + err);
}
}
}, 1000);
});
if (this.db.readyState === 0) await mongoose.connect((0, _classPrivateFieldGet2.default)(this, _dbConnection).url, (0, _classPrivateFieldGet2.default)(this, _dbConnection).options);
return true;
} // Closes connection to the database
async Close() {
mongoose.connection.removeAllListeners();
await mongoose.connection.close();
return true;
}
/**
* @description Get item or entire database.
* @param {String} ENCRYPTIONKEY - Encryptionkey of the database, false if none
* @param {String} collection - The collection to be accessed inside the database.
* @param {Object} [query] - Query for the item you are looking for in the format {type: "type1"}.
*/
async Get(ENCRYPTIONKEY, collection, query) {
if (!collection) throw new Error('Missing collection argument.');
const Model = mongoose.model(collection);
const result = await Model.find(query);
if (ENCRYPTIONKEY) {
for (const i in result) {
const temp = result[i];
result[i] = JSON.parse((await this.Decrypt(result[i].data, result[i].iv, ENCRYPTIONKEY)));
if (temp.createdAt) {
const createdAt = Date.parse(temp.createdAt);
result[i].createdAt = createdAt;
}
}
}
if (result.length === 0) return false;
return result;
}
/**
* @description Insert item in database.
* @param {String} ENCRYPTIONKEY - Encryptionkey of the database, false if none.
* @param {String} collection - The collection to be accessed inside the database.
* @param {Object} item - The item Object you want to insert in the database.
* @param {Object} [index] - Key that should be used as index in case of Encrypted document.
*/
async Insert(ENCRYPTIONKEY, collection, item, index) {
if (!collection || !item || ENCRYPTIONKEY && !index) throw new Error('Missing argument.');
const Model = mongoose.model(collection);
let newDocData = item;
if (ENCRYPTIONKEY) {
const encrypted = await this.Encrypt(JSON.stringify(item), ENCRYPTIONKEY);
newDocData = {
[Object.keys(index)[0]]: Object.values(index)[0],
iv: encrypted.iv,
data: encrypted.data
};
}
const newDoc = new Model(newDocData);
await newDoc.save();
return true;
}
/**
* @description Assign value to item in database
* @param {String} ENCRYPTIONKEY - Encryptionkey of the database, false if none.
* @param {String} collection - The collection to be accessed inside the database.
* @param {Object} query - The entry you want to modify in the format {type: "type1"}.
* @param {Object} modification - The modification you want to make in the format {type: "type2"}.
*/
async Modify(ENCRYPTIONKEY, collection, query, modification) {
if (!collection || !query || !modification) throw new Error('Missing argument.');
const Model = mongoose.model(collection);
let newMod = modification;
if (ENCRYPTIONKEY) {
let result = await Model.findOne(query);
if (result) {
result = JSON.parse((await this.Decrypt(result.data, result.iv, ENCRYPTIONKEY)));
result[Object.keys(modification)[0]] = Object.values(modification)[0];
newMod = await this.Encrypt(JSON.stringify(result), ENCRYPTIONKEY);
}
}
await Model.updateOne(query, newMod);
return true;
}
/**
* @description Delete item in database
* @param {String} collection - The collection to be accessed inside the database.
* @param {Object} query - The entry you want to delete in the format {type: "type1"}.
*/
async Delete(collection, query) {
if (!collection || !query) throw new Error('Missing argument.');
const Model = mongoose.model(collection);
await Model.deleteMany(query);
return true;
}
/**
* @description Encrypts data.
* @param {String} data - Data to be encrypted
* @param {String} secret - Secret used in the encryption
*/
async Encrypt(data, secret) {
const hash = crypto.createHash('sha256');
hash.update(secret);
const key = hash.digest().slice(0, 32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(data);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return {
iv: iv.toString('hex'),
data: encrypted.toString('hex')
};
}
/**
* @description Decrypts data.
* @param {String} data - Data to be decrypted
* @param {String} _iv - Encryption iv
* @param {String} secret - Secret used in the encryption
*/
async Decrypt(data, _iv, secret) {
const hash = crypto.createHash('sha256');
hash.update(secret);
const key = hash.digest().slice(0, 32);
const iv = Buffer.from(_iv, 'hex');
const encryptedText = Buffer.from(data, 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
}
var _dbConnection = new WeakMap();
module.exports = Database;