Browse Source

attachment logic for tribe, rncryptor, modifyMsg

feature/dockerfile-arm
Evan Feenstra 5 years ago
parent
commit
e845a25872
  1. 65
      api/network/modify.ts
  2. 6
      api/network/receive.ts
  3. 5
      api/utils/rncryptor/index.js
  4. 100
      api/utils/rncryptor/rncryptor.js
  5. 3
      dist/api/network/modify.js
  6. 2
      dist/api/network/modify.js.map
  7. 5
      dist/api/utils/rncryptor/index.js
  8. 1
      dist/api/utils/rncryptor/index.js.map
  9. 78
      dist/api/utils/rncryptor/rncryptor.js
  10. 1
      dist/api/utils/rncryptor/rncryptor.js.map
  11. 850
      package-lock.json
  12. 7
      package.json

65
api/network/modify.ts

@ -1,18 +1,71 @@
import * as path from 'path'
import RNCryptor from '../utils/rncryptor'
import * as fetch from 'node-fetch'
import {parseLDAT} from '../utils/ldat'
import * as rsa from '../crypto/rsa'
import * as crypto from 'crypto'
import * as Blob from 'fetch-blob'
const constants = require(path.join(__dirname,'../../config/constants.json'))
const msgtypes = constants.message_types
export function modifyPayload(payload) {
export async function modifyPayload(payload, chat) {
if(payload.type===msgtypes.attachment) {
console.log("MODIFY, ", payload)
// download image from mediaToken
// decrypt key
// decrypt image
// new key, re-encrypt, re-upload
// new payload
const mt = payload.message && payload.message.mediaToken
const key = payload.message && payload.message.mediaKey
const typ = payload.message && payload.message.mediaType
if(!mt || !key) return payload
const terms = parseLDAT(mt)
if(!terms.host) return payload
try {
const r = await fetch(`https://${terms.host}/file/${mt}`)
const buf = await r.buffer()
const decMediaKey = rsa.decrypt(chat.groupPrivateKey, key)
const imgUTF8 = RNCryptor.Decrypt(decMediaKey, buf.toString())
const newKey = crypto.randomBytes(20).toString('hex')
const encImg = RNCryptor.Encrypt(newKey, imgUTF8)
const resp = await fetch(`https://${terms.host}/file`, {
method: 'POST',
body: new Blob([encImg], { type: typ||'image/jpg', name:'file', filename:'Image.jpg' })
})
let json = resp.json()
if(!json.muid) return payload
// PUT NEW TERMS, to finish in personalizeMessage
const amt = terms.meta&&terms.meta.amt
const ttl = terms.meta&&terms.meta.ttl
const mediaTerms: {[k:string]:any} = {
muid:json.muid, ttl:ttl||31536000,
meta:{...amt && {amt}},
skipSigning: amt ? true : false // only sign if its free
}
const encKey = rsa.encrypt(chat.groupKey, newKey)
return fillmsg(payload, {mediaTerms,mediaKey:encKey}) // key is re-encrypted later
} catch(e) {
return payload
}
// how to link w og msg? ogMediaToken?
}
return payload
}
function fillmsg(full, props){
return {
...full, message: {
...full.message,
...props,
}
}
}

6
api/network/receive.ts

@ -48,14 +48,16 @@ async function onReceive(payload){
}
async function forwardMessageToTribe(ogpayload){
const chat = await models.Chat.findOne({where:{uuid:ogpayload.chat.uuid}})
let payload
if(typesToModify.includes(ogpayload.type)){
payload = await modifyPayload(ogpayload)
payload = await modifyPayload(ogpayload, chat)
} else {
payload = ogpayload
}
console.log("FORWARD TO TRIBE",payload)
const chat = await models.Chat.findOne({where:{uuid:payload.chat.uuid}})
//const sender = await models.Contact.findOne({where:{publicKey:payload.sender.pub_key}})
const owner = await models.Contact.findOne({where:{isOwner:true}})
const type = payload.type

5
api/utils/rncryptor/index.js

@ -0,0 +1,5 @@
import {RNCryptor} from './rncryptor'
export default RNCryptor

100
api/utils/rncryptor/rncryptor.js

@ -0,0 +1,100 @@
var sjcl = require('sjcl')
var RNCryptor = {};
/*
Takes password string and salt WordArray
Returns key bitArray
*/
RNCryptor.KeyForPassword = function(password, salt) {
var hmacSHA1 = function (key) {
var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1);
this.encrypt = function () {
return hasher.encrypt.apply(hasher, arguments);
};
};
return sjcl.misc.pbkdf2(password, salt, 10000, 32 * 8, hmacSHA1);
}
/*
Takes password string and plaintext bitArray
options:
iv
encryption_salt
html_salt
Returns ciphertext bitArray
*/
RNCryptor.Encrypt = function(password, plaintext, options) {
options = options || {}
var encryption_salt = options["encryption_salt"] || sjcl.random.randomWords(8 / 4); // FIXME: Need to seed PRNG
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt);
var hmac_salt = options["hmac_salt"] || sjcl.random.randomWords(8 / 4);
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt);
var iv = options["iv"] || sjcl.random.randomWords(16 / 4);
var version = sjcl.codec.hex.toBits("03");
var options = sjcl.codec.hex.toBits("01");
var message = sjcl.bitArray.concat(version, options);
message = sjcl.bitArray.concat(message, encryption_salt);
message = sjcl.bitArray.concat(message, hmac_salt);
message = sjcl.bitArray.concat(message, iv);
var aes = new sjcl.cipher.aes(encryption_key);
// sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
var encrypted = sjcl.mode.cbc.encrypt(aes, plaintext, iv);
message = sjcl.bitArray.concat(message, encrypted);
var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message);
message = sjcl.bitArray.concat(message, hmac);
return sjcl.codec.utf8String.fromBits(message);
}
/*
Takes password string and message (ciphertext) bitArray
options:
iv
encryption_salt
html_salt
Returns plaintext bitArray
*/
RNCryptor.Decrypt = function(password, message, options) {
options = options || {}
var version = sjcl.bitArray.extract(message, 0 * 8, 8);
var options = sjcl.bitArray.extract(message, 1 * 8, 8);
var encryption_salt = sjcl.bitArray.bitSlice(message, 2 * 8, 10 * 8);
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt);
var hmac_salt = sjcl.bitArray.bitSlice(message, 10 * 8, 18 * 8);
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt);
var iv = sjcl.bitArray.bitSlice(message, 18 * 8, 34 * 8);
var ciphertext_end = sjcl.bitArray.bitLength(message) - (32 * 8);
var ciphertext = sjcl.bitArray.bitSlice(message, 34 * 8, ciphertext_end);
var hmac = sjcl.bitArray.bitSlice(message, ciphertext_end);
var expected_hmac = new sjcl.misc.hmac(hmac_key).encrypt(sjcl.bitArray.bitSlice(message, 0, ciphertext_end));
// .equal is of consistent time
if (! sjcl.bitArray.equal(hmac, expected_hmac)) {
throw new sjcl.exception.corrupt("HMAC mismatch or bad password.");
}
var aes = new sjcl.cipher.aes(encryption_key);
// sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
var decrypted = sjcl.mode.cbc.decrypt(aes, ciphertext, iv);
return sjcl.codec.utf8String.fromBits(decrypted);
}
export {RNCryptor}

3
dist/api/network/modify.js

@ -1,8 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const rncryptor_1 = require("../utils/rncryptor");
console.log("RNCryptor", rncryptor_1.default);
const constants = require(path.join(__dirname, '../../config/constants.json'));
const msgtypes = constants.message_types;
// `https://${ldat.host}/file/${media_token}`
function modifyPayload(payload) {
if (payload.type === msgtypes.attachment) {
console.log("MODIFY, ", payload);

2
dist/api/network/modify.js.map

@ -1 +1 @@
{"version":3,"file":"modify.js","sourceRoot":"","sources":["../../../api/network/modify.ts"],"names":[],"mappings":";;AAAA,6BAA4B;AAE5B,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAC,6BAA6B,CAAC,CAAC,CAAA;AAC7E,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAA;AAExC,SAAgB,aAAa,CAAC,OAAO;IACnC,IAAG,OAAO,CAAC,IAAI,KAAG,QAAQ,CAAC,UAAU,EAAE;QACrC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAChC,iCAAiC;QACjC,cAAc;QACd,gBAAgB;QAChB,iCAAiC;QACjC,cAAc;QAEd,sCAAsC;KACvC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAZD,sCAYC"}
{"version":3,"file":"modify.js","sourceRoot":"","sources":["../../../api/network/modify.ts"],"names":[],"mappings":";;AAAA,6BAA4B;AAC5B,kDAA0C;AAE1C,OAAO,CAAC,GAAG,CAAC,WAAW,EAAC,mBAAS,CAAC,CAAA;AAElC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAC,6BAA6B,CAAC,CAAC,CAAA;AAC7E,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAA;AAGxC,6CAA6C;AAG7C,SAAgB,aAAa,CAAC,OAAO;IACnC,IAAG,OAAO,CAAC,IAAI,KAAG,QAAQ,CAAC,UAAU,EAAE;QACrC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAEhC,iCAAiC;QACjC,cAAc;QACd,gBAAgB;QAChB,iCAAiC;QACjC,cAAc;QAEd,sCAAsC;KACvC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAbD,sCAaC"}

5
dist/api/utils/rncryptor/index.js

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const rncryptor_1 = require("./rncryptor");
exports.default = rncryptor_1.RNCryptor;
//# sourceMappingURL=index.js.map

1
dist/api/utils/rncryptor/index.js.map

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../api/utils/rncryptor/index.js"],"names":[],"mappings":";;AAEA,2CAAqC;AAErC,kBAAe,qBAAS,CAAA"}

78
dist/api/utils/rncryptor/rncryptor.js

@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var sjcl = require('sjcl');
var RNCryptor = {};
exports.RNCryptor = RNCryptor;
/*
Takes password string and salt WordArray
Returns key bitArray
*/
RNCryptor.KeyForPassword = function (password, salt) {
var hmacSHA1 = function (key) {
var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1);
this.encrypt = function () {
return hasher.encrypt.apply(hasher, arguments);
};
};
return sjcl.misc.pbkdf2(password, salt, 10000, 32 * 8, hmacSHA1);
};
/*
Takes password string and plaintext bitArray
options:
iv
encryption_salt
html_salt
Returns ciphertext bitArray
*/
RNCryptor.Encrypt = function (password, plaintext, options) {
options = options || {};
var encryption_salt = options["encryption_salt"] || sjcl.random.randomWords(8 / 4); // FIXME: Need to seed PRNG
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt);
var hmac_salt = options["hmac_salt"] || sjcl.random.randomWords(8 / 4);
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt);
var iv = options["iv"] || sjcl.random.randomWords(16 / 4);
var version = sjcl.codec.hex.toBits("03");
var options = sjcl.codec.hex.toBits("01");
var message = sjcl.bitArray.concat(version, options);
message = sjcl.bitArray.concat(message, encryption_salt);
message = sjcl.bitArray.concat(message, hmac_salt);
message = sjcl.bitArray.concat(message, iv);
var aes = new sjcl.cipher.aes(encryption_key);
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
var encrypted = sjcl.mode.cbc.encrypt(aes, plaintext, iv);
message = sjcl.bitArray.concat(message, encrypted);
var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message);
message = sjcl.bitArray.concat(message, hmac);
return message;
};
/*
Takes password string and message (ciphertext) bitArray
options:
iv
encryption_salt
html_salt
Returns plaintext bitArray
*/
RNCryptor.Decrypt = function (password, message, options) {
options = options || {};
var version = sjcl.bitArray.extract(message, 0 * 8, 8);
var options = sjcl.bitArray.extract(message, 1 * 8, 8);
var encryption_salt = sjcl.bitArray.bitSlice(message, 2 * 8, 10 * 8);
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt);
var hmac_salt = sjcl.bitArray.bitSlice(message, 10 * 8, 18 * 8);
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt);
var iv = sjcl.bitArray.bitSlice(message, 18 * 8, 34 * 8);
var ciphertext_end = sjcl.bitArray.bitLength(message) - (32 * 8);
var ciphertext = sjcl.bitArray.bitSlice(message, 34 * 8, ciphertext_end);
var hmac = sjcl.bitArray.bitSlice(message, ciphertext_end);
var expected_hmac = new sjcl.misc.hmac(hmac_key).encrypt(sjcl.bitArray.bitSlice(message, 0, ciphertext_end));
// .equal is of consistent time
if (!sjcl.bitArray.equal(hmac, expected_hmac)) {
throw new sjcl.exception.corrupt("HMAC mismatch or bad password.");
}
var aes = new sjcl.cipher.aes(encryption_key);
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
var decrypted = sjcl.mode.cbc.decrypt(aes, ciphertext, iv);
return decrypted;
};
//# sourceMappingURL=rncryptor.js.map

1
dist/api/utils/rncryptor/rncryptor.js.map

@ -0,0 +1 @@
{"version":3,"file":"rncryptor.js","sourceRoot":"","sources":["../../../../api/utils/rncryptor/rncryptor.js"],"names":[],"mappings":";;AAAA,IAAI,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AAE1B,IAAI,SAAS,GAAG,EAAE,CAAC;AAiGX,8BAAS;AA/FjB;;;EAGE;AAEF,SAAS,CAAC,cAAc,GAAG,UAAS,QAAQ,EAAE,IAAI;IAChD,IAAI,QAAQ,GAAG,UAAU,GAAG;QACxB,IAAI,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG;YACX,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC,CAAC;IACN,CAAC,CAAC;IACF,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AACnE,CAAC,CAAA;AAED;;;;;;;EAOE;AACF,SAAS,CAAC,OAAO,GAAG,UAAS,QAAQ,EAAE,SAAS,EAAE,OAAO;IACvD,OAAO,GAAG,OAAO,IAAI,EAAE,CAAA;IACvB,IAAI,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,2BAA2B;IAC/G,IAAI,cAAc,GAAG,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAEzE,IAAI,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACvE,IAAI,QAAQ,GAAG,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAE7D,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAE1D,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE1C,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACrD,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IACzD,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACnD,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAE5C,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,qEAAqE,CAAC,EAAE,CAAC;IACrF,IAAI,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IAE1D,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAEnD,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE9C,OAAO,OAAO,CAAC;AACjB,CAAC,CAAA;AAED;;;;;;;EAOE;AACF,SAAS,CAAC,OAAO,GAAG,UAAS,QAAQ,EAAE,OAAO,EAAE,OAAO;IACrD,OAAO,GAAG,OAAO,IAAI,EAAE,CAAA;IAEvB,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACvD,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEvD,IAAI,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACrE,IAAI,cAAc,GAAG,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAEzE,IAAI,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IAChE,IAAI,QAAQ,GAAG,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAE7D,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IAEzD,IAAI,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAEjE,IAAI,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC;IAEzE,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAE3D,IAAI,aAAa,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;IAE7G,+BAA+B;IAC/B,IAAI,CAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE;QAC9C,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;KACpE;IAED,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,qEAAqE,CAAC,EAAE,CAAC;IACrF,IAAI,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;IAE3D,OAAO,SAAS,CAAC;AACnB,CAAC,CAAA"}

850
package-lock.json

File diff suppressed because it is too large

7
package.json

@ -41,6 +41,7 @@
"decamelize": "^3.1.1",
"express": "^4.16.4",
"express-winston": "^4.0.2",
"fetch-blob": "^1.0.5",
"fs": "^0.0.1-security",
"grpc": "^1.24.2",
"helmet": "^3.21.1",
@ -63,11 +64,13 @@
"reflect-metadata": "^0.1.13",
"request": "^2.88.0",
"request-promise": "^4.2.5",
"rncryptor-js": "0.0.1",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^0.88.1",
"sequelize": "^5.19.3",
"sequelize-cli": "^5.5.1",
"sequelize-typescript": "^1.1.0",
"sjcl": "^1.0.8",
"tail": "^2.0.3",
"ts-node": "^8.5.4",
"tsc": "^1.20150623.0",
@ -82,14 +85,14 @@
"babel-polyfill": "^6.26.0",
"mini-css-extract-plugin": "^0.8.0",
"mocha": "^6.2.0",
"node-sass": "^4.13.0",
"node-sass": "^4.14.1",
"nodemon": "^2.0.1",
"sass-loader": "^8.0.0",
"sqlite3": "^4.2.0",
"style-loader": "^1.0.0",
"webpack": "^4.41.0",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2"
"webpack-dev-server": "^3.11.0"
},
"pkg": {
"assets": [

Loading…
Cancel
Save