@vmm/service
Advanced tools
Comparing version 1.0.89 to 1.0.91
{ | ||
"name": "@vmm/service", | ||
"version": "1.0.89", | ||
"version": "1.0.91", | ||
"main": "src/index.js", | ||
@@ -5,0 +5,0 @@ "description": "VMM Service library", |
275
src/Argv.js
@@ -1,154 +0,151 @@ | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
'use strict'; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function (mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.Argv = void 0; | ||
const lodash_1 = __importDefault(require("lodash")); | ||
const lodash_1 = __importDefault(require('lodash')); | ||
class Argv { | ||
_appInfo; | ||
_error; | ||
_argDefines; | ||
_args = {}; | ||
_parsedArgs = {}; | ||
constructor(argDefines, appInfo) { | ||
this._appInfo = appInfo; | ||
this._argDefines = argDefines; | ||
this._addDefaultArg(); | ||
this._initDefaultValues(); | ||
this._parse(); | ||
this._applyCmdDefines(); | ||
constructor(argDefines, appInfo) { | ||
this._args = {}; | ||
this._parsedArgs = {}; | ||
this._appInfo = appInfo; | ||
this._argDefines = argDefines; | ||
this._addDefaultArg(); | ||
this._initDefaultValues(); | ||
this._parse(); | ||
this._applyCmdDefines(); | ||
} | ||
_initDefaultValues() { | ||
for (const k of Object.keys(this._argDefines)) { | ||
this._args[k] = this._argDefines[k].default; | ||
} | ||
_initDefaultValues() { | ||
for (const k of Object.keys(this._argDefines)) { | ||
this._args[k] = this._argDefines[k].default; | ||
} | ||
_addDefaultArg() { | ||
this._argDefines['help'] = { | ||
short: 'h', | ||
description: 'show help message', | ||
default: false, | ||
}; | ||
this._argDefines['version'] = { | ||
short: 'v', | ||
description: 'show current version', | ||
default: false, | ||
}; | ||
} | ||
_parse() { | ||
let lastArg = null; | ||
this._parsedArgs = {}; | ||
process.argv.forEach((arg, i) => { | ||
if (i === 0 || i === 1) return; | ||
if (arg.startsWith('-')) { | ||
this._parsedArgs[arg] = true; | ||
lastArg = arg; | ||
} else { | ||
if (lastArg) { | ||
this._parsedArgs[lastArg] = arg; | ||
} | ||
} | ||
}); | ||
} | ||
_setParseError(err) { | ||
if (this._error) { | ||
this._error = this._error + '\n' + err; | ||
} else { | ||
this._error = err; | ||
} | ||
_addDefaultArg() { | ||
this._argDefines["help"] = { | ||
short: "h", | ||
description: "show help message", | ||
default: false | ||
}; | ||
this._argDefines["version"] = { | ||
short: "v", | ||
description: "show current version", | ||
default: false | ||
}; | ||
} | ||
_parse() { | ||
let lastArg = null; | ||
this._parsedArgs = {}; | ||
process.argv.forEach((arg, i) => { | ||
if (i === 0 || i === 1) | ||
return; | ||
if (arg.startsWith("-")) { | ||
this._parsedArgs[arg] = true; | ||
lastArg = arg; | ||
} | ||
else { | ||
if (lastArg) { | ||
this._parsedArgs[lastArg] = arg; | ||
} | ||
} | ||
}); | ||
} | ||
_setParseError(err) { | ||
if (this._error) { | ||
this._error = this._error + "\n" + err; | ||
} | ||
_setArgValue(key, value) { | ||
switch (typeof this._argDefines[key].default) { | ||
case 'string': | ||
if (typeof value === 'string') { | ||
this._args[key] = value; | ||
} else { | ||
this._setParseError(`Error Arg: ${key}`); | ||
} | ||
else { | ||
this._error = err; | ||
} | ||
} | ||
_setArgValue(key, value) { | ||
switch (typeof this._argDefines[key].default) { | ||
case "string": | ||
if (typeof value === "string") { | ||
this._args[key] = value; | ||
} | ||
else { | ||
this._setParseError(`Error Arg: ${key}`); | ||
} | ||
break; | ||
case "boolean": | ||
if (typeof value === "boolean") { | ||
this._args[key] = value; | ||
} | ||
else if (typeof value === "string") { | ||
switch (value.toLowerCase()) { | ||
case "yes": | ||
case "true": | ||
case "1": | ||
this._args[key] = true; | ||
break; | ||
default: | ||
this._args[key] = false; | ||
} | ||
} | ||
break; | ||
case "number": | ||
this._args[key] = Number(value); | ||
break; | ||
break; | ||
case 'boolean': | ||
if (typeof value === 'boolean') { | ||
this._args[key] = value; | ||
} else if (typeof value === 'string') { | ||
switch (value.toLowerCase()) { | ||
case 'yes': | ||
case 'true': | ||
case '1': | ||
this._args[key] = true; | ||
break; | ||
default: | ||
this._setParseError(`Error Arg: ${key}`); | ||
this._args[key] = false; | ||
} | ||
} | ||
break; | ||
case 'number': | ||
this._args[key] = Number(value); | ||
break; | ||
default: | ||
this._setParseError(`Error Arg: ${key}`); | ||
} | ||
_applyCmdDefines() { | ||
for (const key of Object.keys(this._argDefines)) { | ||
const longArg = `--${lodash_1.default.kebabCase(key)}`; | ||
const shortArg = `-${this._argDefines[key].short}`; | ||
if (this._parsedArgs[longArg]) { | ||
this._setArgValue(key, this._parsedArgs[longArg]); | ||
this._parsedArgs[`--${lodash_1.default.kebabCase(key)}`] = undefined; | ||
} | ||
else if (this._parsedArgs[shortArg]) { | ||
this._setArgValue(key, this._parsedArgs[shortArg]); | ||
this._parsedArgs[`-${this._argDefines[key].short}`] = undefined; | ||
} | ||
} | ||
} | ||
_applyCmdDefines() { | ||
for (const key of Object.keys(this._argDefines)) { | ||
const longArg = `--${lodash_1.default.kebabCase(key)}`; | ||
const shortArg = `-${this._argDefines[key].short}`; | ||
if (this._parsedArgs[longArg]) { | ||
this._setArgValue(key, this._parsedArgs[longArg]); | ||
this._parsedArgs[`--${lodash_1.default.kebabCase(key)}`] = undefined; | ||
} else if (this._parsedArgs[shortArg]) { | ||
this._setArgValue(key, this._parsedArgs[shortArg]); | ||
this._parsedArgs[`-${this._argDefines[key].short}`] = undefined; | ||
} | ||
} | ||
_printHelp() { | ||
if (this._error) { | ||
console.log("ERROR:" + this._error); | ||
} | ||
if (this._appInfo) { | ||
console.log(this._appInfo.name + " v" + this._appInfo.version); | ||
console.log(this._appInfo.description); | ||
} | ||
console.log("Arguments:"); | ||
lodash_1.default.forEach(this._argDefines, (arg, key) => { | ||
console.log(` -${arg.short} [--${lodash_1.default.kebabCase(key)}], ${arg.description} ,default: ${arg.default}`); | ||
}); | ||
} | ||
_printHelp() { | ||
if (this._error) { | ||
console.log('ERROR:' + this._error); | ||
} | ||
check(bExit) { | ||
lodash_1.default.forEach(this._parsedArgs, (arg, key) => { | ||
if (arg !== undefined) { | ||
this._setParseError(`invalid arg: ${key}`); | ||
} | ||
}); | ||
if (this._args["help"]) { | ||
this._printHelp(); | ||
if (bExit) { | ||
process.exit(1); | ||
} | ||
} | ||
if (this._args["version"]) { | ||
console.log(this._appInfo.version); | ||
if (bExit) { | ||
process.exit(1); | ||
} | ||
} | ||
if (this._error) { | ||
this._printHelp(); | ||
if (bExit) { | ||
process.exit(1); | ||
} | ||
} | ||
return this; | ||
if (this._appInfo) { | ||
console.log(this._appInfo.name + ' v' + this._appInfo.version); | ||
console.log(this._appInfo.description); | ||
} | ||
args() { | ||
return this._args; | ||
console.log('Arguments:'); | ||
lodash_1.default.forEach(this._argDefines, (arg, key) => { | ||
console.log( | ||
` -${arg.short} [--${lodash_1.default.kebabCase(key)}], ${ | ||
arg.description | ||
} ,default: ${arg.default}`, | ||
); | ||
}); | ||
} | ||
check(bExit) { | ||
lodash_1.default.forEach(this._parsedArgs, (arg, key) => { | ||
if (arg !== undefined) { | ||
this._setParseError(`invalid arg: ${key}`); | ||
} | ||
}); | ||
if (this._args['help']) { | ||
this._printHelp(); | ||
if (bExit) { | ||
process.exit(1); | ||
} | ||
} | ||
if (this._args['version']) { | ||
console.log(this._appInfo.version); | ||
if (bExit) { | ||
process.exit(1); | ||
} | ||
} | ||
if (this._error) { | ||
this._printHelp(); | ||
if (bExit) { | ||
process.exit(1); | ||
} | ||
} | ||
return this; | ||
} | ||
args() { | ||
return this._args; | ||
} | ||
} | ||
exports.Argv = Argv; |
@@ -1,140 +0,168 @@ | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
'use strict'; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function (mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.Mongo = void 0; | ||
const mongodb_1 = require("mongodb"); | ||
const lodash_1 = __importDefault(require("lodash")); | ||
const logger_1 = require("./logger"); | ||
const mongodb_1 = require('mongodb'); | ||
const lodash_1 = __importDefault(require('lodash')); | ||
const logger_1 = require('./logger'); | ||
const log = (0, logger_1.logger)(`@vmm/service:MongoDb`); | ||
class Mongo { | ||
_url; | ||
_options; | ||
_modName; | ||
_client = null; | ||
_db = null; | ||
_collections = {}; | ||
_dbCollectionsDefine; | ||
constructor(url, options, modName, dbCollectionsDefine) { | ||
log.debug('using db defines:', Object.keys(dbCollectionsDefine)); | ||
this._dbCollectionsDefine = dbCollectionsDefine; | ||
this._url = url; | ||
this._options = options; | ||
this._modName = modName; | ||
constructor(url, options, modName, dbCollectionsDefine) { | ||
this._client = null; | ||
this._db = null; | ||
this._collections = {}; | ||
log.debug('using db defines:', Object.keys(dbCollectionsDefine)); | ||
this._dbCollectionsDefine = dbCollectionsDefine; | ||
this._url = url; | ||
this._options = options; | ||
this._modName = modName; | ||
} | ||
getMongoClient() { | ||
if (this._client === null) throw new Error('Mongodb client Invalid!!'); | ||
return this._client; | ||
} | ||
collections() { | ||
return this._collections; | ||
} | ||
async connect() { | ||
log.debug('connect to mongodb'); | ||
this._client = await mongodb_1.MongoClient.connect( | ||
this._url, | ||
this._options, | ||
); | ||
this._db = await this._client.db(this._modName); | ||
this._monitorDbEvent(); | ||
await this._ensureSchemaCollections(); | ||
lodash_1.default.forEach(this._collections, async (coll, name) => { | ||
await this._ensureCollectionIndexes( | ||
coll, | ||
this._dbCollectionsDefine[name].indexSchema, | ||
); | ||
}); | ||
log.debug('open mongodb successed'); | ||
} | ||
async _ensureSchemaCollections() { | ||
log.debug('_ensureSchemaCollections:'); | ||
if (!this._db) return; | ||
const curColls = lodash_1.default.keyBy( | ||
await this._db.collections(), | ||
'collectionName', | ||
); | ||
const modCollDefines = lodash_1.default.pickBy( | ||
this._dbCollectionsDefine, | ||
(v) => !lodash_1.default.get(v.collOptions, '_extDb'), | ||
); | ||
log.debug('modCollDefines:', Object.keys(modCollDefines)); | ||
const externCollDefines = lodash_1.default.pickBy( | ||
this._dbCollectionsDefine, | ||
(v) => !!lodash_1.default.get(v.collOptions, '_extDb'), | ||
); | ||
log.debug('externCollDefines:', externCollDefines); | ||
for (const colName of Object.keys(curColls)) { | ||
if (modCollDefines[colName]) { | ||
log.debug('open existed collection:', colName); | ||
this._collections[colName] = curColls[colName]; | ||
} else { | ||
if (!colName.startsWith('_') && !colName.startsWith('system.')) { | ||
const d = new Date(); | ||
const name = | ||
'_unused_' + | ||
colName + | ||
`_${d.getFullYear()}_${d.getMonth()}_${d.getDate()}`; | ||
await this._db.renameCollection(colName, name); | ||
log.info('rename unused collection:', name); | ||
} else { | ||
log.info('unused collection:', colName); | ||
} | ||
} | ||
} | ||
getMongoClient() { | ||
if (this._client === null) | ||
throw new Error('Mongodb client Invalid!!'); | ||
return this._client; | ||
for (const newColl of lodash_1.default.difference( | ||
Object.keys(modCollDefines), | ||
Object.keys(curColls), | ||
)) { | ||
this._collections[newColl] = await this._db.createCollection( | ||
newColl, | ||
this._dbCollectionsDefine[newColl].collOptions, | ||
); | ||
log.debug('create new collection:', newColl); | ||
} | ||
collections() { | ||
return this._collections; | ||
for (const k of Object.keys(externCollDefines)) { | ||
const v = externCollDefines[k]; | ||
if (!v) continue; | ||
const extDbInfo = lodash_1.default.get(v, 'collOptions._extDb'); | ||
if (!this._client || !extDbInfo) return; | ||
const externDb = this._client.db(extDbInfo.db); | ||
const extColls = lodash_1.default.keyBy( | ||
await externDb.collections(), | ||
'collectionName', | ||
); | ||
if (!extColls[extDbInfo.col]) { | ||
log.debug('create extern collection ok:', extDbInfo); | ||
this._collections[k] = await externDb.createCollection( | ||
extDbInfo.col, | ||
lodash_1.default.omit(v.collOptions, '_extDb'), | ||
); | ||
} else { | ||
log.debug('open extern collection ok:', extDbInfo); | ||
this._collections[k] = extColls[extDbInfo.col]; | ||
} | ||
} | ||
async connect() { | ||
log.debug('connect to mongodb'); | ||
this._client = await mongodb_1.MongoClient.connect(this._url, this._options); | ||
this._db = await this._client.db(this._modName); | ||
this._monitorDbEvent(); | ||
await this._ensureSchemaCollections(); | ||
lodash_1.default.forEach(this._collections, async (coll, name) => { | ||
await this._ensureCollectionIndexes(coll, this._dbCollectionsDefine[name].indexSchema); | ||
}); | ||
log.debug('open mongodb successed'); | ||
} | ||
async _ensureCollectionIndexes(coll, indexSchemas) { | ||
if (lodash_1.default.isEmpty(indexSchemas)) { | ||
return; | ||
} | ||
async _ensureSchemaCollections() { | ||
log.debug('_ensureSchemaCollections:'); | ||
if (!this._db) | ||
return; | ||
const curColls = lodash_1.default.keyBy(await this._db.collections(), 'collectionName'); | ||
const modCollDefines = lodash_1.default.pickBy(this._dbCollectionsDefine, (v) => !lodash_1.default.get(v.collOptions, '_extDb')); | ||
log.debug('modCollDefines:', Object.keys(modCollDefines)); | ||
const externCollDefines = lodash_1.default.pickBy(this._dbCollectionsDefine, (v) => !!lodash_1.default.get(v.collOptions, '_extDb')); | ||
log.debug('externCollDefines:', externCollDefines); | ||
for (const colName of Object.keys(curColls)) { | ||
if (modCollDefines[colName]) { | ||
log.debug('open existed collection:', colName); | ||
this._collections[colName] = curColls[colName]; | ||
} | ||
else { | ||
if (!colName.startsWith('_') && !colName.startsWith('system.')) { | ||
const d = new Date(); | ||
const name = '_unused_' + colName + `_${d.getFullYear()}_${d.getMonth()}_${d.getDate()}`; | ||
await this._db.renameCollection(colName, name); | ||
log.info('rename unused collection:', name); | ||
} | ||
else { | ||
log.info('unused collection:', colName); | ||
} | ||
} | ||
} | ||
for (const newColl of lodash_1.default.difference(Object.keys(modCollDefines), Object.keys(curColls))) { | ||
this._collections[newColl] = await this._db.createCollection(newColl, this._dbCollectionsDefine[newColl].collOptions); | ||
log.debug('create new collection:', newColl); | ||
} | ||
for (const k of Object.keys(externCollDefines)) { | ||
const v = externCollDefines[k]; | ||
if (!v) | ||
continue; | ||
const extDbInfo = lodash_1.default.get(v, 'collOptions._extDb'); | ||
if (!this._client || !extDbInfo) | ||
return; | ||
const externDb = this._client.db(extDbInfo.db); | ||
const extColls = lodash_1.default.keyBy(await externDb.collections(), 'collectionName'); | ||
if (!extColls[extDbInfo.col]) { | ||
log.debug('create extern collection ok:', extDbInfo); | ||
this._collections[k] = await externDb.createCollection(extDbInfo.col, lodash_1.default.omit(v.collOptions, '_extDb')); | ||
} | ||
else { | ||
log.debug('open extern collection ok:', extDbInfo); | ||
this._collections[k] = extColls[extDbInfo.col]; | ||
} | ||
} | ||
const indexsArray = await coll.indexes(); | ||
const indexes = lodash_1.default.keyBy(indexsArray, 'name'); | ||
log.debug( | ||
'ensure collection indexes:', | ||
coll.collectionName, | ||
Object.keys(indexSchemas).join(), | ||
); | ||
for (const key of Object.keys(indexes)) { | ||
if (key.startsWith('_id')) continue; | ||
if (!lodash_1.default.isPlainObject(indexSchemas[key])) { | ||
await coll.dropIndex(key); | ||
delete indexes[key]; | ||
log.info('drop invalid index:', coll.collectionName, key); | ||
} | ||
} | ||
async _ensureCollectionIndexes(coll, indexSchemas) { | ||
if (lodash_1.default.isEmpty(indexSchemas)) { | ||
return; | ||
} | ||
const indexsArray = await coll.indexes(); | ||
const indexes = lodash_1.default.keyBy(indexsArray, 'name'); | ||
log.debug('ensure collection indexes:', coll.collectionName, Object.keys(indexSchemas).join()); | ||
for (const key of Object.keys(indexes)) { | ||
if (key.startsWith('_id')) | ||
continue; | ||
if (!lodash_1.default.isPlainObject(indexSchemas[key])) { | ||
await coll.dropIndex(key); | ||
delete indexes[key]; | ||
log.info('drop invalid index:', coll.collectionName, key); | ||
} | ||
} | ||
for (const key of Object.keys(indexSchemas)) { | ||
if (lodash_1.default.isEmpty(indexes[key])) { | ||
log.info('create new index:', coll.collectionName, key, indexSchemas[key]); | ||
await coll.createIndex(indexSchemas[key].fields, { | ||
name: key, | ||
...indexSchemas[key].options, | ||
}); | ||
} | ||
} | ||
} | ||
_monitorDbEvent() { | ||
if (!this._db) | ||
return; | ||
this._db.on('drop', () => { | ||
log.debug('mongodb drop:'); | ||
for (const key of Object.keys(indexSchemas)) { | ||
if (lodash_1.default.isEmpty(indexes[key])) { | ||
log.info( | ||
'create new index:', | ||
coll.collectionName, | ||
key, | ||
indexSchemas[key], | ||
); | ||
await coll.createIndex(indexSchemas[key].fields, { | ||
name: key, | ||
...indexSchemas[key].options, | ||
}); | ||
this._db.on('close', () => { | ||
log.debug('mongodb close:'); | ||
}); | ||
this._db.on('error', (err) => { | ||
log.debug('mongodb error:', err); | ||
}); | ||
this._db.on('timeout', () => { | ||
log.debug('mongodb timeout:'); | ||
}); | ||
this._db.on('reconnect', () => { | ||
log.debug('mongodb reconnect:'); | ||
}); | ||
} | ||
} | ||
} | ||
_monitorDbEvent() { | ||
if (!this._db) return; | ||
this._db.on('drop', () => { | ||
log.debug('mongodb drop:'); | ||
}); | ||
this._db.on('close', () => { | ||
log.debug('mongodb close:'); | ||
}); | ||
this._db.on('error', (err) => { | ||
log.debug('mongodb error:', err); | ||
}); | ||
this._db.on('timeout', () => { | ||
log.debug('mongodb timeout:'); | ||
}); | ||
this._db.on('reconnect', () => { | ||
log.debug('mongodb reconnect:'); | ||
}); | ||
} | ||
} | ||
exports.Mongo = Mongo; |
@@ -1,316 +0,371 @@ | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
'use strict'; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function (mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.Service = exports.SessionCrypto = exports.SESS_START = void 0; | ||
const express_1 = __importDefault(require("express")); | ||
const body_parser_1 = __importDefault(require("body-parser")); | ||
const logger_1 = require("./logger"); | ||
const express_1 = __importDefault(require('express')); | ||
const body_parser_1 = __importDefault(require('body-parser')); | ||
const logger_1 = require('./logger'); | ||
const log = (0, logger_1.logger)(`@vmm/service:Service`); | ||
const crypto_1 = __importDefault(require("crypto")); | ||
const assert_1 = __importDefault(require("assert")); | ||
const http_1 = __importDefault(require("http")); | ||
const lodash_1 = __importDefault(require("lodash")); | ||
const base64url_1 = __importDefault(require("base64url")); | ||
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); | ||
const restDbApi_1 = require("./restDbApi"); | ||
const crypto_1 = __importDefault(require('crypto')); | ||
const assert_1 = __importDefault(require('assert')); | ||
const http_1 = __importDefault(require('http')); | ||
const lodash_1 = __importDefault(require('lodash')); | ||
const base64url_1 = __importDefault(require('base64url')); | ||
const jsonwebtoken_1 = __importDefault(require('jsonwebtoken')); | ||
const restDbApi_1 = require('./restDbApi'); | ||
exports.SESS_START = '-cs-'; | ||
class SessionCrypto { | ||
_key; | ||
constructor(key) { | ||
(0, assert_1.default)(key.length > 0); | ||
let k = key; | ||
while (k.length < 32) { | ||
k = k + k; | ||
} | ||
this._key = Buffer.from(k.slice(0, 32)); | ||
constructor(key) { | ||
(0, assert_1.default)(key.length > 0); | ||
let k = key; | ||
while (k.length < 32) { | ||
k = k + k; | ||
} | ||
dec(data) { | ||
let ret = {}; | ||
try { | ||
const dec = crypto_1.default.createDecipheriv('aes-256-cbc', this._key, this._key.slice(16)); | ||
const buf = base64url_1.default.toBuffer(data); | ||
ret = JSON.parse(Buffer.concat([dec.update(buf), dec.final()]).toString()); | ||
} | ||
catch (e) { | ||
log.error('dec session fail:', e.message); | ||
throw 'session crypto fail'; | ||
} | ||
return ret; | ||
this._key = Buffer.from(k.slice(0, 32)); | ||
} | ||
dec(data) { | ||
let ret = {}; | ||
try { | ||
const dec = crypto_1.default.createDecipheriv( | ||
'aes-256-cbc', | ||
this._key, | ||
this._key.slice(16), | ||
); | ||
const buf = base64url_1.default.toBuffer(data); | ||
ret = JSON.parse( | ||
Buffer.concat([dec.update(buf), dec.final()]).toString(), | ||
); | ||
} catch (e) { | ||
log.error('dec session fail:', e.message); | ||
throw 'session crypto fail'; | ||
} | ||
enc(data) { | ||
(0, assert_1.default)(lodash_1.default.isPlainObject(data)); | ||
const enc = crypto_1.default.createCipheriv('aes-256-cbc', this._key, this._key.slice(16)); | ||
const encBuf = Buffer.concat([ | ||
enc.update(JSON.stringify(data)), | ||
enc.final(), | ||
]); | ||
return base64url_1.default.encode(encBuf); | ||
} | ||
return ret; | ||
} | ||
enc(data) { | ||
(0, assert_1.default)(lodash_1.default.isPlainObject(data)); | ||
const enc = crypto_1.default.createCipheriv( | ||
'aes-256-cbc', | ||
this._key, | ||
this._key.slice(16), | ||
); | ||
const encBuf = Buffer.concat([ | ||
enc.update(JSON.stringify(data)), | ||
enc.final(), | ||
]); | ||
return base64url_1.default.encode(encBuf); | ||
} | ||
} | ||
exports.SessionCrypto = SessionCrypto; | ||
class Service { | ||
expressApp = (0, express_1.default)(); | ||
router = express_1.default.Router(); | ||
_options; | ||
_name; | ||
_sessionCrypto; | ||
_servicePath; | ||
api; | ||
httpServer; | ||
constructor(options) { | ||
if (lodash_1.default.isEmpty(options.projectConfig.sessKey)) { | ||
options.projectConfig.sessKey = crypto_1.default.randomBytes(8).toString('hex'); | ||
options.projectConfig._save(); | ||
} | ||
log.debug('---> using sessKey:', options.projectConfig.sessKey); | ||
options.projectConfig._event.on('load', () => { | ||
log.debug('---> load sessKey:', options.projectConfig.sessKey); | ||
this._sessionCrypto = new SessionCrypto(options.projectConfig.sessKey); | ||
constructor(options) { | ||
this.expressApp = (0, express_1.default)(); | ||
this.router = express_1.default.Router(); | ||
if (lodash_1.default.isEmpty(options.projectConfig.sessKey)) { | ||
options.projectConfig.sessKey = crypto_1.default | ||
.randomBytes(8) | ||
.toString('hex'); | ||
options.projectConfig._save(); | ||
} | ||
log.debug('---> using sessKey:', options.projectConfig.sessKey); | ||
options.projectConfig._event.on('load', () => { | ||
log.debug('---> load sessKey:', options.projectConfig.sessKey); | ||
this._sessionCrypto = new SessionCrypto(options.projectConfig.sessKey); | ||
}); | ||
this._sessionCrypto = new SessionCrypto(options.projectConfig.sessKey); | ||
if (lodash_1.default.isEmpty(options.host)) { | ||
options.host = '127.0.0.1'; | ||
} | ||
if (lodash_1.default.isEmpty(options.sessionExpire)) { | ||
options.sessionExpire = 8 * 60 * 60; | ||
} | ||
this._name = options.api.pkg.name; | ||
this._options = options; | ||
this._servicePath = `/api/${lodash_1.default | ||
.kebabCase(this._name) | ||
.replace(/-(api|service)$/, '')}`; | ||
this.api = lodash_1.default.clone(options.api.api); | ||
log.debug('api using service path :', this._servicePath); | ||
this.router.use(this._responseHeaderOriginHandle.bind(this)); | ||
this.router.use('/_ping', this._pingHandle.bind(this)); | ||
this.router.use(this._logHandle.bind(this)); | ||
if (options.customApi) { | ||
Object.keys(options.customApi).forEach((k) => { | ||
log.debug('-- using customApi:', `/${lodash_1.default.kebabCase(k)}`); | ||
this.router.use( | ||
`/${lodash_1.default.kebabCase(k)}`, | ||
options.customApi[k], | ||
); | ||
}); | ||
} | ||
this.router.use( | ||
body_parser_1.default.json({ | ||
type: 'application/json', | ||
limit: '500kb', | ||
}), | ||
); | ||
Object.keys(this.api).forEach((apiName) => { | ||
if (apiName.startsWith('_')) return; | ||
log.debug( | ||
'using api:', | ||
apiName, | ||
this._servicePath + '/' + lodash_1.default.kebabCase(apiName), | ||
); | ||
this.router.use( | ||
`/${lodash_1.default.kebabCase(apiName)}`, | ||
this._createApiHandle( | ||
this._options.api.api[apiName], | ||
() => this.api[apiName], | ||
), | ||
); | ||
}); | ||
if (this._options.api.db) { | ||
lodash_1.default.forEach(this._options.api.db, (restDb, apiName) => { | ||
const dbApis = (0, restDbApi_1.createDbApi)( | ||
this._options.mongo, | ||
restDb, | ||
); | ||
lodash_1.default.forEach(restDb, (dbMethod, methodName) => { | ||
const apiPath = `/db/${lodash_1.default.kebabCase( | ||
apiName, | ||
)}/${lodash_1.default.kebabCase(methodName)}`; | ||
this.router.use( | ||
apiPath, | ||
this._createApiHandle(dbMethod, () => dbApis[methodName]), | ||
); | ||
}); | ||
this._sessionCrypto = new SessionCrypto(options.projectConfig.sessKey); | ||
if (lodash_1.default.isEmpty(options.host)) { | ||
options.host = '127.0.0.1'; | ||
} | ||
if (lodash_1.default.isEmpty(options.sessionExpire)) { | ||
options.sessionExpire = 8 * 60 * 60; | ||
} | ||
this._name = options.api.pkg.name; | ||
this._options = options; | ||
this._servicePath = `/api/${lodash_1.default.kebabCase(this._name).replace(/-(api|service)$/, '')}`; | ||
this.api = lodash_1.default.clone(options.api.api); | ||
log.debug('api using service path :', this._servicePath); | ||
this.router.use(this._responseHeaderOriginHandle.bind(this)); | ||
this.router.use('/_ping', this._pingHandle.bind(this)); | ||
this.router.use(this._logHandle.bind(this)); | ||
if (options.customApi) { | ||
Object.keys(options.customApi).forEach((k) => { | ||
log.debug('-- using customApi:', `/${lodash_1.default.kebabCase(k)}`); | ||
console.log('=========', options.customApi[k]); | ||
this.router.use(`/${lodash_1.default.kebabCase(k)}`, options.customApi[k]); | ||
}); | ||
} | ||
this.router.use(body_parser_1.default.json({ | ||
type: 'application/json', | ||
limit: '500kb', | ||
})); | ||
Object.keys(this.api).forEach((apiName) => { | ||
if (apiName.startsWith('_')) | ||
return; | ||
log.debug('using api:', apiName, this._servicePath + '/' + lodash_1.default.kebabCase(apiName)); | ||
this.router.use(`/${lodash_1.default.kebabCase(apiName)}`, this._createApiHandle(this._options.api.api[apiName], () => this.api[apiName])); | ||
}); | ||
if (this._options.api.db) { | ||
lodash_1.default.forEach(this._options.api.db, (restDb, apiName) => { | ||
const dbApis = (0, restDbApi_1.createDbApi)(this._options.mongo, restDb); | ||
lodash_1.default.forEach(restDb, (dbMethod, methodName) => { | ||
const apiPath = `/db/${lodash_1.default.kebabCase(apiName)}/${lodash_1.default.kebabCase(methodName)}`; | ||
this.router.use(apiPath, this._createApiHandle(dbMethod, () => dbApis[methodName])); | ||
}); | ||
log.debug('using rest db api:', apiName, Object.keys(restDb).join()); | ||
}); | ||
} | ||
this.expressApp.use(body_parser_1.default.urlencoded({ extended: false })); | ||
this.expressApp.use(body_parser_1.default.json()); | ||
this.expressApp.use(this._servicePath, this.router); | ||
this.httpServer = http_1.default.createServer(this.expressApp); | ||
log.debug('using rest db api:', apiName, Object.keys(restDb).join()); | ||
}); | ||
} | ||
_responseHeaderOriginHandle(req, res, next) { | ||
res.header('Access-Control-Allow-Origin', req.header('origin')); | ||
res.header('Access-Control-Allow-Credentials', 'true'); | ||
if (req.header('Access-Control-Request-Method')) { | ||
res.header('Access-Control-Allow-Method', req.header('Access-Control-Request-Method')); | ||
this.expressApp.use(body_parser_1.default.urlencoded({ extended: false })); | ||
this.expressApp.use(body_parser_1.default.json()); | ||
this.expressApp.use(this._servicePath, this.router); | ||
this.httpServer = http_1.default.createServer(this.expressApp); | ||
} | ||
_responseHeaderOriginHandle(req, res, next) { | ||
res.header('Access-Control-Allow-Origin', req.header('origin')); | ||
res.header('Access-Control-Allow-Credentials', 'true'); | ||
if (req.header('Access-Control-Request-Method')) { | ||
res.header( | ||
'Access-Control-Allow-Method', | ||
req.header('Access-Control-Request-Method'), | ||
); | ||
} | ||
if (req.header('Access-Control-Request-Headers')) { | ||
res.header( | ||
'Access-Control-Allow-Headers', | ||
req.header('Access-Control-Request-Headers'), | ||
); | ||
} | ||
let aceHeaders = ''; | ||
lodash_1.default.forEach(req.headers, (v, k) => { | ||
if (k.startsWith(exports.SESS_START)) { | ||
if (aceHeaders.length == 0) { | ||
aceHeaders = aceHeaders + k; | ||
} else { | ||
aceHeaders = aceHeaders + ', ' + k; | ||
} | ||
if (req.header('Access-Control-Request-Headers')) { | ||
res.header('Access-Control-Allow-Headers', req.header('Access-Control-Request-Headers')); | ||
} | ||
let aceHeaders = ''; | ||
lodash_1.default.forEach(req.headers, (v, k) => { | ||
if (k.startsWith(exports.SESS_START)) { | ||
if (aceHeaders.length == 0) { | ||
aceHeaders = aceHeaders + k; | ||
} | ||
else { | ||
aceHeaders = aceHeaders + ', ' + k; | ||
} | ||
} | ||
}); | ||
res.header('Access-Control-Expose-Headers', aceHeaders); | ||
res.header('Access-Control-Allow-Credentials', 'true'); | ||
if (req.method === 'OPTIONS') { | ||
res.status(204).end(); | ||
return; | ||
} | ||
next(); | ||
} | ||
}); | ||
res.header('Access-Control-Expose-Headers', aceHeaders); | ||
res.header('Access-Control-Allow-Credentials', 'true'); | ||
if (req.method === 'OPTIONS') { | ||
res.status(204).end(); | ||
return; | ||
} | ||
_logHandle(req, res, next) { | ||
log.debug('http request:', req.socket.remoteAddress, req.originalUrl, req.headers); | ||
next(); | ||
next(); | ||
} | ||
_logHandle(req, res, next) { | ||
log.debug( | ||
'http request:', | ||
req.socket.remoteAddress, | ||
req.originalUrl, | ||
req.headers, | ||
); | ||
next(); | ||
} | ||
_pingHandle(req, res, next) { | ||
res.status(200).json({ ret: 'ok' }); | ||
} | ||
buildClientSession(name, sess) { | ||
sess.iss = name; | ||
delete sess.iat; | ||
delete sess.exp; | ||
sess.server = this._sessionCrypto.enc(sess.server || {}); | ||
log.debug('update jwt session ', name, sess); | ||
const s = jsonwebtoken_1.default.sign( | ||
sess, | ||
this._options.projectConfig.sessKey, | ||
{ | ||
expiresIn: | ||
sess._expire > 0 ? sess._expire : this._options.sessionExpire, | ||
}, | ||
); | ||
return s; | ||
} | ||
decoderClientSession(sessName, csData) { | ||
if (lodash_1.default.isEmpty(csData.trim())) { | ||
return null; | ||
} | ||
_pingHandle(req, res, next) { | ||
res.status(200).json({ ret: 'ok' }); | ||
let sess; | ||
try { | ||
sess = jsonwebtoken_1.default.verify( | ||
csData, | ||
this._options.projectConfig.sessKey, | ||
); | ||
} catch (e) { | ||
log.error('verify jwt fail:', sessName, e.message); | ||
return null; | ||
} | ||
buildClientSession(name, sess) { | ||
sess.iss = name; | ||
delete sess.iat; | ||
delete sess.exp; | ||
sess.server = this._sessionCrypto.enc(sess.server || {}); | ||
log.debug('update jwt session ', name, sess); | ||
const s = jsonwebtoken_1.default.sign(sess, this._options.projectConfig.sessKey, { | ||
expiresIn: sess._expire > 0 ? sess._expire : this._options.sessionExpire, | ||
}); | ||
return s; | ||
try { | ||
sess.server = this._sessionCrypto.dec(sess.server); | ||
} catch (e) { | ||
log.error('decrypt session server fail:', sessName, csData); | ||
return null; | ||
} | ||
decoderClientSession(sessName, csData) { | ||
if (lodash_1.default.isEmpty(csData.trim())) { | ||
return null; | ||
if (sess.iss !== sessName) { | ||
log.error(`jwt session iss fail,sess=${sessName},iss=${sess.iss}`); | ||
return null; | ||
} | ||
return sess; | ||
} | ||
_parseSession(req) { | ||
const cs = {}; | ||
const fnGetCs = (obj) => { | ||
if (typeof obj !== 'object') return {}; | ||
const ret = {}; | ||
lodash_1.default.forEach(obj, (v, k) => { | ||
if (k.startsWith(exports.SESS_START)) { | ||
ret[k.slice(4)] = v; | ||
} | ||
let sess; | ||
try { | ||
sess = jsonwebtoken_1.default.verify(csData, this._options.projectConfig.sessKey); | ||
} | ||
catch (e) { | ||
log.error('verify jwt fail:', sessName, e.message); | ||
return null; | ||
} | ||
try { | ||
sess.server = this._sessionCrypto.dec(sess.server); | ||
} | ||
catch (e) { | ||
log.error('decrypt session server fail:', sessName, csData); | ||
return null; | ||
} | ||
if (sess.iss !== sessName) { | ||
log.error(`jwt session iss fail,sess=${sessName},iss=${sess.iss}`); | ||
return null; | ||
} | ||
return sess; | ||
}); | ||
return ret; | ||
}; | ||
lodash_1.default.assign(cs, fnGetCs(req.headers), fnGetCs(req.query)); | ||
const retcs = {}; | ||
lodash_1.default.forEach(cs, (sessData, sessName) => { | ||
const _sessName = lodash_1.default.camelCase(sessName); | ||
const sess = this.decoderClientSession(_sessName, sessData); | ||
if (sess) { | ||
retcs[_sessName] = sess; | ||
} | ||
}); | ||
return retcs; | ||
} | ||
_checkApiPremissions(definedApi, sessions) { | ||
log.debug('check api premissions: need=', definedApi._actor); | ||
if (lodash_1.default.isEmpty(definedApi._actor)) { | ||
return true; | ||
} | ||
_parseSession(req) { | ||
const cs = {}; | ||
const fnGetCs = (obj) => { | ||
if (typeof obj !== 'object') | ||
return {}; | ||
const ret = {}; | ||
lodash_1.default.forEach(obj, (v, k) => { | ||
if (k.startsWith(exports.SESS_START)) { | ||
ret[k.slice(4)] = v; | ||
} | ||
}); | ||
return ret; | ||
}; | ||
lodash_1.default.assign(cs, fnGetCs(req.headers), fnGetCs(req.query)); | ||
const retcs = {}; | ||
lodash_1.default.forEach(cs, (sessData, sessName) => { | ||
const _sessName = lodash_1.default.camelCase(sessName); | ||
const sess = this.decoderClientSession(_sessName, sessData); | ||
if (sess) { | ||
retcs[_sessName] = sess; | ||
} | ||
}); | ||
return retcs; | ||
let needActors = []; | ||
if (lodash_1.default.isString(definedApi._actor)) { | ||
needActors.push(definedApi._actor); | ||
} else if (lodash_1.default.isArray(definedApi._actor)) { | ||
needActors = definedApi._actor; | ||
} else { | ||
log.error( | ||
'invalid defined API actors', | ||
JSON.stringify(definedApi, null, 2), | ||
); | ||
return false; | ||
} | ||
_checkApiPremissions(definedApi, sessions) { | ||
log.debug('check api premissions: need=', definedApi._actor); | ||
if (lodash_1.default.isEmpty(definedApi._actor)) { | ||
return true; | ||
console.log('----sessions', sessions); | ||
const userActors = lodash_1.default.get(sessions, [ | ||
'user', | ||
'client', | ||
'actor', | ||
]); | ||
if (lodash_1.default.isEmpty(userActors)) { | ||
log.error('invalid user actor', userActors); | ||
return false; | ||
} | ||
if ( | ||
lodash_1.default.intersection(needActors, userActors).length !== | ||
needActors.length | ||
) { | ||
log.error('invalid user actors,need:', needActors, ',user:', userActors); | ||
return false; | ||
} | ||
log.debug('check user premissions successed. ', userActors); | ||
return true; | ||
} | ||
_createApiHandle(preDefinedApi, createApiFunc) { | ||
return async (req, res) => { | ||
log.debug('api handle:', req.originalUrl); | ||
try { | ||
const sessions = this._parseSession(req); | ||
for (const name of preDefinedApi._sessions) { | ||
(0, assert_1.default)( | ||
this._options.api.session[name], | ||
`session define of ${name} not existed`, | ||
); | ||
if (!lodash_1.default.isPlainObject(sessions[name])) { | ||
log.debug('set default sessions:', name); | ||
sessions[name] = lodash_1.default.cloneDeep( | ||
this._options.api.session[name], | ||
); | ||
} | ||
Object.defineProperty(sessions[name], 'update', { | ||
value: function (expireSeconds) { | ||
this._updateFlag = true; | ||
this._expire = expireSeconds; | ||
}, | ||
enumerable: false, | ||
}); | ||
Object.defineProperty(sessions[name], '_updateFlag', { | ||
value: false, | ||
enumerable: false, | ||
writable: true, | ||
}); | ||
Object.defineProperty(sessions[name], '_expire', { | ||
value: -1, | ||
enumerable: false, | ||
writable: true, | ||
}); | ||
} | ||
let needActors = []; | ||
if (lodash_1.default.isString(definedApi._actor)) { | ||
needActors.push(definedApi._actor); | ||
if (!this._checkApiPremissions(preDefinedApi, sessions)) { | ||
res.status(401).end('invalid user actor for ' + req.originalUrl); | ||
return; | ||
} | ||
else if (lodash_1.default.isArray(definedApi._actor)) { | ||
needActors = definedApi._actor; | ||
let result = {}; | ||
const apiFunc = createApiFunc(); | ||
if (typeof apiFunc === 'function') { | ||
result = await apiFunc(req.body, sessions, req, res); | ||
} else { | ||
throw new Error('service api invalid'); | ||
} | ||
else { | ||
log.error('invalid defined API actors', JSON.stringify(definedApi, null, 2)); | ||
return false; | ||
if (result['_res_end']) { | ||
res.end(); | ||
return; | ||
} | ||
console.log('----sessions', sessions); | ||
const userActors = lodash_1.default.get(sessions, ['user', 'client', 'actor']); | ||
if (lodash_1.default.isEmpty(userActors)) { | ||
log.error('invalid user actor', userActors); | ||
return false; | ||
lodash_1.default.forEach(sessions, (sess, name) => { | ||
if (!sess._updateFlag) return; | ||
const s = this.buildClientSession(name, sess); | ||
res.header( | ||
`${exports.SESS_START}${lodash_1.default.kebabCase(name)}`, | ||
s, | ||
); | ||
}); | ||
res.json(result); | ||
} catch (e) { | ||
if (typeof e === 'object') { | ||
res.status(e.status ? e.status : 500).end(e.message); | ||
} else if (typeof e === 'string') { | ||
res.status(500).end(e); | ||
} else { | ||
res.status(500).end('unknown error'); | ||
} | ||
if (lodash_1.default.intersection(needActors, userActors).length !== needActors.length) { | ||
log.error('invalid user actors,need:', needActors, ',user:', userActors); | ||
return false; | ||
} | ||
log.debug('check user premissions successed. ', userActors); | ||
return true; | ||
} | ||
_createApiHandle(preDefinedApi, createApiFunc) { | ||
return async (req, res) => { | ||
log.debug('api handle:', req.originalUrl); | ||
try { | ||
const sessions = this._parseSession(req); | ||
for (const name of preDefinedApi._sessions) { | ||
(0, assert_1.default)(this._options.api.session[name], `session define of ${name} not existed`); | ||
if (!lodash_1.default.isPlainObject(sessions[name])) { | ||
log.debug('set default sessions:', name); | ||
sessions[name] = lodash_1.default.cloneDeep(this._options.api.session[name]); | ||
} | ||
Object.defineProperty(sessions[name], 'update', { | ||
value: function (expireSeconds) { | ||
this._updateFlag = true; | ||
this._expire = expireSeconds; | ||
}, | ||
enumerable: false, | ||
}); | ||
Object.defineProperty(sessions[name], '_updateFlag', { | ||
value: false, | ||
enumerable: false, | ||
writable: true, | ||
}); | ||
Object.defineProperty(sessions[name], '_expire', { | ||
value: -1, | ||
enumerable: false, | ||
writable: true, | ||
}); | ||
} | ||
if (!this._checkApiPremissions(preDefinedApi, sessions)) { | ||
res.status(401).end('invalid user actor for ' + req.originalUrl); | ||
return; | ||
} | ||
let result = {}; | ||
const apiFunc = createApiFunc(); | ||
if (typeof apiFunc === 'function') { | ||
result = await apiFunc(req.body, sessions, req, res); | ||
} | ||
else { | ||
throw new Error('service api invalid'); | ||
} | ||
if (result['_res_end']) { | ||
res.end(); | ||
return; | ||
} | ||
lodash_1.default.forEach(sessions, (sess, name) => { | ||
if (!sess._updateFlag) | ||
return; | ||
const s = this.buildClientSession(name, sess); | ||
res.header(`${exports.SESS_START}${lodash_1.default.kebabCase(name)}`, s); | ||
}); | ||
res.json(result); | ||
} | ||
catch (e) { | ||
if (typeof e === 'object') { | ||
res.status(e.status ? e.status : 500).end(e.message); | ||
} | ||
else if (typeof e === 'string') { | ||
res.status(500).end(e); | ||
} | ||
else { | ||
res.status(500).end('unknown error'); | ||
} | ||
log.error('api exec fail:', e); | ||
} | ||
}; | ||
} | ||
async listen() { | ||
this.httpServer.listen(this._options.port, '0.0.0.0', 20, () => { | ||
log.info('service listen in port successed:', this._options.port); | ||
}); | ||
} | ||
log.error('api exec fail:', e); | ||
} | ||
}; | ||
} | ||
async listen() { | ||
this.httpServer.listen(this._options.port, '0.0.0.0', 20, () => { | ||
log.info('service listen in port successed:', this._options.port); | ||
}); | ||
} | ||
} | ||
exports.Service = Service; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
1246
44274