Comparing version 1.0.18 to 1.0.19
@@ -9,2 +9,3 @@ //Low level connection to PostgreSQL database | ||
//X- Split low level code out from UI module | ||
//- First time you run a source build on a db build from canned schema, it fails once, then fixes itself | ||
//- If schema initialization SQL fails, and we created the DB, should we then delete it? | ||
@@ -11,0 +12,0 @@ //- If Postgres restarted, we lose our connection. Is there a way to recover/reconnect? |
@@ -6,3 +6,4 @@ //Copyright WyattERP.org; See license in root of this package | ||
Wyseman: require('./wyseman.js'), | ||
dbClient: require('./dbclient.js') | ||
dbClient: require('./dbclient.js'), | ||
Client: require('./client.js') | ||
} |
@@ -8,13 +8,7 @@ //Manage the connection between a User Interface and the backend database | ||
//X- Allow separate instance of all variables per ws connection | ||
//- First time you run a source build on a db build from canned schema, it fails once, then fixes itself | ||
//- Should any of the %s's in pg-format really be %L's? | ||
//- Allow to specify 'returning' fields to insert, update overriding default * | ||
//- Handle warnings from the database, in addition to errors? (like if row count = 0) | ||
//- Query to return meta-data for table joins | ||
//- How to handle module-specific control functions? | ||
//- Return query promise in case no callback given? | ||
//- Restructure code as explained in: https://github.com/websockets/ws/issues/377#issuecomment-462152231 | ||
//- | ||
//- Extend module to also listen for clients on regular TCP socket | ||
//- ? Restructure code as explained in: https://github.com/websockets/ws/issues/377#issuecomment-462152231 | ||
const DbClient = require('./dbclient.js') //PostgreSQL | ||
const Handler = require('./handler.js') //JSON..SQL | ||
const Ws = require('ws') //Web sockets | ||
@@ -24,7 +18,6 @@ const Https = require('https') | ||
const Url = require('url') | ||
const Format = require('pg-format') //String formatting/escaping | ||
const Base64 = require('base64-js') | ||
const Crypto = require('crypto') | ||
const Net = require('net') | ||
const Opers = ['=', '!=', '<', '<=', '>', '>=', '~', 'diff', 'in', 'isnull', 'notnull', 'true'] | ||
const PemHeader = "-----BEGIN PUBLIC KEY-----\n" | ||
@@ -38,392 +31,158 @@ const PemFooter = "\n-----END PUBLIC KEY-----" | ||
module.exports = class Wyseman { | ||
constructor(dbConf, wsConf, adminConf) { | ||
let { port, credentials, actions, dispatch, expApp} = wsConf | ||
, log = this.log = dbConf.log || wsConf.log || adminConf.log || require('./log') | ||
, context = {db:null, control:null, actions, dispatch, expApp, log} | ||
, server = credentials ? Https.createServer(credentials) : Http.createServer() | ||
, adminDB = new DbClient(adminConf) //Admin access to the DB | ||
, validateToken = (user, token, pub, listen, payload, cb) => { //Validate user with one-time login token | ||
log.debug("Request to validate:", user, "tok:", token) | ||
adminDB.query('select base.validate_token($1,$2,$3) as valid', [user, token, pub], (err, res)=>{ | ||
if (err) log.error("Error validating user:", user, token, err) | ||
let valid = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].valid : false | ||
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options | ||
log.debug(" valid result:", valid) | ||
cb(valid) | ||
}) | ||
} | ||
, validateSignature = (user, sign, message, listen, payload, cb) => { //Validate a user with an existing key | ||
log.trace("Validate:", user, sign, message) | ||
adminDB.query('select conn_pub from base.ent_v where username = $1', [user], (err, res)=>{ | ||
if (err) log.error("Error getting user connection key:", user, err) | ||
let pubKey = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].conn_pub : null | ||
, valid = false //Assume failure | ||
log.trace(" public key:", pubKey, res ? res.rows : null) | ||
if (pubKey && sign) { //We have the public key from the DB and the signed hash from the client | ||
let rawKey = Buffer.from(pubKey, 'hex') //Hex-to-binary | ||
, rawSig = Buffer.from(sign, 'hex') | ||
, key = PemHeader + Base64.fromByteArray(rawKey) + PemFooter //Raw-to-PEM | ||
, verify = Crypto.createVerify('SHA256') //Make a verifier | ||
log.trace(" user public:", user, key) | ||
verify.update(message) //Give it our message | ||
valid = verify.verify(Object.assign({key}, VerifyTpt), rawSig) //And check it | ||
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options | ||
} | ||
log.debug(" valid:", valid) | ||
cb(valid) | ||
}) | ||
} | ||
, wss = new Ws.Server({ //Initiate a new websocket connection | ||
constructor(dbConf, sockConf, adminConf) { | ||
let { websock, sock, actions, dispatch, expApp } = sockConf | ||
, { port, credentials, delta } = websock | ||
, server = credentials ? Https.createServer(credentials) : Http.createServer() //websocket rides on this server | ||
, wsport = port | ||
this.log = dbConf.log || sockConf.log || adminConf.log || require('./log') //Try to find a logger | ||
this.adminDB = new DbClient(adminConf) //Open Admin connection to the DB | ||
this.maxDelta = delta | ||
//For future noise-protocol connection: | ||
// if (sock) | ||
// Net.createServer(sock => this.sockConnect(sock)).listen(sock) | ||
if (wsport) { | ||
let wss = new Ws.Server({ //Initiate a new websocket server | ||
server, | ||
clientTracking: true, | ||
verifyClient: function(info, cb) { | ||
log.debug("verifyClient:", info) | ||
let { origin, req, secure } = info | ||
, query = Url.parse(req.url, true).query | ||
, { user, db, sign, date, token, pub } = query | ||
, listen = db ? JSON.parse(Buffer.from(db,'hex').toString()) : null | ||
, payload = req.WysemanPayload = {} //Custom Wyseman data to pass back to connection | ||
log.trace("Checking client:", origin, "cb:", !!cb, "q:", query, "s:", secure, "IP:", req.connection.remoteAddress, "listen:", listen, typeof(listen)) | ||
if (user && token && pub) | ||
validateToken(user, token, pub, listen, payload, (valid)=>{ | ||
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect | ||
}) | ||
else if (user && sign && date) { | ||
let message = JSON.stringify({ip: req.connection.remoteAddress, cookie: req.headers.cookie, userAgent: req.headers['user-agent'], date}) | ||
, now = new Date() | ||
, msgDate = new Date(date) | ||
log.debug("Check dates:", now, msgDate, wsConf, "Time delta:", now - msgDate) | ||
if (wsConf.delta && Math.abs(now - msgDate) > wsConf.delta) | ||
cb(false, 400, 'Invalid Date Stamp') | ||
else validateSignature(user, sign, message, listen, payload, (valid)=>{ | ||
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect | ||
}) | ||
} else if (user && !secure) { | ||
Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options | ||
cb(true) //On an insecure/debug web connection | ||
} else | ||
cb(false, 401, 'No login credentials') //tell websocket not to connect | ||
}, | ||
verifyClient: (info, cb) => { | ||
try {this.verifyClient(info, cb)} catch(e) { | ||
this.log.error("Verifying client:", e) | ||
} | ||
} | ||
}) | ||
log.info("Wyseman listening:", port) | ||
if (port) server.listen(port) | ||
this.log.info("Wyseman listening on websocket:", wsport) | ||
wss.on('connection', (ws, req) => { //When connection from view client is open | ||
let payload = req.WysemanPayload | ||
, config = Object.assign({}, dbConf, payload) //user,listen passed from verifyClient | ||
, ctx = Object.assign({}, context) //Private copy for this instance | ||
log.verbose("WS Connected; User:", config.user, config) | ||
if (!config.user) return //Shouldn't be able to get here without a username | ||
ctx.db = new DbClient(config, (channel, message, mine) => { | ||
let data = JSON.parse(message) | ||
this.log.trace("Async notify from DB:", channel, data, mine) | ||
ws.send(JSON.stringify({action: 'notify', channel, data}), err => { | ||
if (err) this.log.error(err) | ||
if (!wss) return | ||
server.listen(wsport) | ||
wss.on('connection', (ws, req) => { //When connection from view client is open | ||
let payload = req.WysemanPayload | ||
, config = Object.assign({}, dbConf, payload) //user,listen was passed to us from verifyClient | ||
this.log.verbose("WS Connected; User:", config.user, config) | ||
if (!config.user) return //Shouldn't be able to get here without a username | ||
let db = new DbClient(config, (channel, message, mine) => { | ||
let data = JSON.parse(message) | ||
this.log.trace("Async notify from DB:", channel, data, mine) | ||
ws.send(JSON.stringify({action: 'notify', channel, data}), err => { | ||
if (err) this.log.error(err) | ||
}) | ||
}) | ||
, handler = new Handler({db, control:null, actions, dispatch, expApp, log:this.log}) | ||
this.log.trace("Wyseman connection conf:", "Client WS port:", wsport) | ||
ws.on('close', (code, reason) => { | ||
this.log.debug("Wyseman socket connection closed:", code, reason) | ||
db.disconnect() //Free up this DB connection | ||
}) | ||
}) | ||
this.log.trace("Wyseman connection conf:", "Client port:", port) | ||
ws.on('close', (code, reason) => { | ||
this.log.debug("Wyseman socket connection closed:", code, reason) | ||
ctx.db.disconnect() //Free up this DB connection | ||
}) | ||
ws.on('message', (imsg) => { //When message received from client | ||
this.log.trace("Incoming Wyseman message:" + imsg + ";") | ||
let packet = JSON.parse(imsg) | ||
this.handler(packet, ctx, (omsg) => { //Handle/control an incoming packet | ||
let jmsg = JSON.stringify(omsg) | ||
ws.on('message', (imsg) => { //When message received from client | ||
this.log.trace("Incoming Wyseman message:" + imsg + ";") | ||
let packet = JSON.parse(imsg) | ||
handler.handle(packet, (omsg) => { //Handle/control an incoming packet | ||
let jmsg = JSON.stringify(omsg) | ||
//this.log.trace('Sending back:', JSON.stringify(omsg, null, 2)) | ||
ws.send(jmsg, err => { //Send a reply back to the client | ||
if (err) this.log.error(err) | ||
ws.send(jmsg, err => { //Send a reply back to the client | ||
if (err) this.log.error(err) | ||
}) | ||
}) | ||
}) | ||
}) | ||
this.log.debug("Connected clients: ", wss.clients.size) | ||
}) //wss.on connection | ||
}) //wss.on connection | ||
} //if (wsport) | ||
} //constructor | ||
// Log an error and generate an error object | ||
// ------------------------------------------------------------------- | ||
error(message, err = 'unknown') { | ||
let ret = { message }, prefix = '!wylib.data.' | ||
this.log.trace("Query error " + ret.message + ": " + err) | ||
if (typeof err == 'string') { | ||
if (err && err.split('.').length == 1) ret.code = prefix + err | ||
} else if (typeof err == 'object') { | ||
if (err.constraint && err.constraint.match(/^!/)) { | ||
ret.code = err.constraint | ||
} else if (err.code) { | ||
ret.code = prefix + err.code | ||
} else { | ||
ret.code = prefix + 'unknown' | ||
} | ||
if (err.message) ret.message += (': ' + err.message) | ||
if (err.detail) ret.detail = err.detail | ||
} | ||
return ret | ||
} | ||
// Handle an incoming packet from the view client | ||
// ------------------------------------------------------------------- | ||
handler(msg, ctx, sender) { | ||
this.log.trace("Wyseman packet handler, msg:", JSON.stringify(msg)) | ||
let {id, view, action} = msg | ||
if (!view) return | ||
if (action == 'lang') { | ||
action = 'tuple'; | ||
Object.assign(msg, {fields: ['title','help','columns','messages'], table: 'wm.table_lang', where: {obj: view, language: msg.language || 'en'}}) | ||
} else if (msg.action == 'meta') { | ||
action = 'tuple' | ||
Object.assign(msg, {fields: ['obj','pkey','cols','columns','styles','fkeys'], table: 'wm.table_meta', where: {obj: view}}) | ||
} | ||
let {table, params, fields, where, order} = msg, argtypes | ||
//this.log.debug(" From msg, table:", table, " view:", view, "order: ", order) | ||
let [sch, tab] = (table || view).split('.') //Split into schema and table | ||
if (!tab) {tab = sch; sch = 'public'} //Default to public if no schema specified | ||
;([tab, argtypes] = tab.split(/[\(\)]/)) //In case table is specified as a function | ||
this.log.trace(" tab:", tab, " argtypes:", argtypes) | ||
table = Format.ident(sch) + '.' + Format.ident(tab) | ||
if (argtypes) argtypes = argtypes.split(',') | ||
let tuples = 1, result = {query: null, parms: [], error: null} | ||
try { switch (action) { | ||
case 'tuple': | ||
this.buildSelect(result, {fields, table, argtypes, params, where}); break; | ||
case 'select': | ||
this.buildSelect(result, {fields, table, argtypes, params, where, order}) | ||
tuples = null; break; | ||
case 'update': | ||
this.buildUpdate(result, fields, table, where); break; | ||
case 'insert': | ||
this.buildInsert(result, fields, table); break; | ||
case 'delete': | ||
this.buildDelete(result, table, where) | ||
tuples = 0; break; | ||
default: | ||
if (!ctx.control && ctx.dispatch) | ||
ctx.control = new ctx.dispatch(ctx.expApp, ctx.db, ctx.actions, ctx.log) //Start a controller just in time | ||
if (ctx.control && ctx.control.handle && ctx.control.handle(msg, sender)) return | ||
result.error = this.error('unknown action: ' + action, 'badAction') | ||
}} catch(e) { | ||
result.error = this.error('parsing: ' + e.message) | ||
this.log.error(e.message, e.stack) | ||
} | ||
if (result.error) { | ||
sender({error: result.error, id, view, action}) | ||
return | ||
} | ||
let { query, parms } = result | ||
if (!query) return //Ignore null queries (result of an error in query builder) | ||
parms.forEach((p,x)=>{ //node-pg tries to convert JS arrays to PS arrays. We don't want that. | ||
if (typeof p == 'object') parms[x] = JSON.stringify(p) | ||
}) | ||
ctx.db.query(query, parms, (err, res) => { //Run the user's query | ||
this.log.trace(" query:", query, "parms:", parms, "tuples:", tuples, "Err:", err) | ||
if (err) { | ||
msg.error = this.error("from database", err) | ||
} else if (!res) { | ||
msg.error = this.error("no result") | ||
} else if (tuples && res.rowCount != tuples) { | ||
msg.error = this.error("unexpected rows: " + res.rowCount + ' != ' + tuples, "badTuples") | ||
} | ||
if (res && res.rows) { | ||
if (tuples == 1) msg.data = res.rows[0] | ||
else if (tuples == null || tuples > 0) msg.data = res.rows | ||
else if (tuples == 0) msg.data = null | ||
} | ||
if (sender) sender(msg) | ||
}) | ||
} | ||
//Validate a user who is presenting a one-time connection token | ||
// ----------------------------------------------------------------------------- | ||
buildSelect(res, spec) { | ||
let { fields, table, argtypes, params, where, order} = spec | ||
this.log.trace("BuildSelect", fields, table, argtypes, params, where, order) | ||
let wh = '', ord = '' | ||
, whereText = this.buildWhere(where, res) | ||
, ordText = this.buildOrder(order, res) | ||
if (where && whereText) wh = ' where ' + whereText | ||
if (order && ordText) ord = ' order by ' + ordText | ||
if (params) { //If selecting from a function | ||
let i = res.parms.length + 1, plist = [] | ||
params.forEach(param => { //Form parameter list | ||
plist.push(Format("$%s%s", i++, argtypes && argtypes.length > 0 ? '::'+argtypes.shift() : '')) | ||
res.parms.push(param) | ||
}) | ||
table = table + "(" + plist.join(',') + ")" //And attach the parameter list to the end of the table to make the function call | ||
} | ||
if (fields && fields != "*") fields = Format.ident(fields) | ||
res.query = Format('select %s%s%s%s;', fields ? fields + ' from ' : '', table, wh, ord) | ||
this.log.trace("buildSelect:", res.query, "parms:", res.parms) | ||
} | ||
// ----------------------------------------------------------------------------- | ||
buildInsert(res, fields, table) { | ||
let i = res.parms.length + 1 //Starting parameter number | ||
let flist = [] | ||
let plist = [] | ||
Object.keys(fields).forEach(fld => { | ||
if (fld === null || fld === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null} | ||
flist.push(Format("%I", fld)) | ||
plist.push(Format("$%s", i++)) | ||
res.parms.push(fields[fld]) | ||
validateToken(user, token, pub, listen, payload, cb) { | ||
this.log.debug("Request to validate:", user, "tok:", token) | ||
this.adminDB.query('select base.validate_token($1,$2,$3) as valid', [user, token, pub], (err, res) => { | ||
if (err) this.log.error("Error validating user:", user, token, err) | ||
let valid = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].valid : false | ||
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options | ||
this.log.debug(" valid result:", valid) | ||
cb(valid) | ||
}) | ||
if (flist.length <= 0) { | ||
res.error = this.error("empty insert", 'badInsert'); return null | ||
} | ||
res.query = Format('insert into %s (%s) values (%s) returning *;', table, flist.join(', '), plist.join(', ')) | ||
this.log.trace("buildInsert:", res.query, "\n parms:", res.parms) | ||
} | ||
//Validate a user who has an existing key | ||
// ----------------------------------------------------------------------------- | ||
buildUpdate(res, fields, table, where) { | ||
let wh = this.buildWhere(where, res), | ||
i = res.parms.length + 1 //Starting parameter number | ||
if (!where || !wh || res.parms.length <= 0) { | ||
res.error = this.error("empty where clause", 'badWhere'); return | ||
} | ||
let flist = [] | ||
Object.keys(fields).forEach(fld => { | ||
if (fld === null || fld === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null} | ||
flist.push(Format("%I = $%s", fld, i++)) | ||
res.parms.push(fields[fld]) | ||
}) | ||
if (flist.length <= 0) { | ||
res.error = this.error("empty update", "badUpdate"); return null | ||
} | ||
res.query = Format('update %s set %s where %s returning *;', table, flist.join(', '), wh) | ||
this.log.trace("buildUpdate:", res.query, "\n parms:", res.parms) | ||
validateSignature(user, sign, message, listen, payload, cb) { | ||
this.log.debug("Validate:", user, sign, message) | ||
this.adminDB.query('select conn_pub from base.ent_v where username = $1', [user], (err, res) => { try { | ||
if (err) this.log.error("Error getting user connection key:", user, err) | ||
let pubKey = (!err && res && res.rows && res.rows.length >= 1) ? res.rows[0].conn_pub : null | ||
, valid = false //Assume failure | ||
this.log.trace(" public key:", pubKey, res ? res.rows : null) | ||
if (pubKey && sign) { //We have the public key from the DB and the signed hash from the client | ||
let rawKey = Buffer.from(pubKey, 'hex') //Hex-to-binary | ||
, rawSig = Buffer.from(sign, 'hex') | ||
, key = PemHeader + Base64.fromByteArray(rawKey) + PemFooter //Raw-to-PEM | ||
, verify = Crypto.createVerify('SHA256') //Make a verifier | ||
this.log.trace(" user public:", user, key) | ||
verify.update(message) //Give it our message | ||
valid = verify.verify(Object.assign({key}, VerifyTpt), rawSig) //And check it | ||
if (valid) Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options | ||
} | ||
this.log.debug(" valid:", valid) | ||
cb(valid) | ||
} catch (e) { | ||
this.log.debug("Validating signature:", e) | ||
}}) | ||
} | ||
//Validate a user who is trying to connect | ||
// ----------------------------------------------------------------------------- | ||
buildDelete(res, table, where) { | ||
let wh = this.buildWhere(where, res) | ||
if (!where || !wh || res.parms.length <= 0) { | ||
res.error = this.error("unbounded delete", 'badDelete'); return null | ||
} | ||
res.query = Format('delete from %s where %s;', table, wh) | ||
//this.log.trace("buildDelete:", query, "\n parms:", res.parms) | ||
} | ||
verifyClient(info, cb) { | ||
this.log.debug("verifyClient:", info.req.headers) | ||
let { origin, req, secure } = info | ||
, query = Url.parse(req.url, true).query | ||
, { user, db, sign, date, token, pub } = query | ||
, listen = db ? JSON.parse(Buffer.from(db,'hex').toString()) : null | ||
, payload = req.WysemanPayload = {} //Custom Wyseman data to pass back to connection | ||
// Create a where clause from a JSON structure | ||
// ----------------------------------------------------------------------------- | ||
buildWhere(logic, res) { | ||
//this.log.trace("Logic:", logic, typeof logic) | ||
if (!logic) return null | ||
let i = res.parms.length + 1 //Starting parameter number | ||
if (Array.isArray(logic)) { //Compact form, each element is: field <oper> value, anded together | ||
let clauses = []; | ||
logic.forEach(log => { | ||
let [ left, oper, right ] = Array.isArray(log) ? log : log.split(' ') | ||
//this.log.trace("Left:", left, "Oper:", oper, "Right:", right) | ||
if (left === null || left === undefined) {res.error = this.error("invalid null or undefined left hand side", 'badLeftSide'); return null} | ||
if (!Opers.includes(oper)) {res.error = this.error("invalid operator: " + oper, 'badOperator'); return null} | ||
if (oper == 'diff') oper = 'is distinct from' | ||
if (oper == 'isnull' || oper == 'notnull') | ||
clauses.push(Format("%I %s", left, oper)) | ||
else if (oper == 'true') | ||
clauses.push(Format("%I", left)) | ||
else { | ||
clauses.push(Format("%I %s $%s", left, oper, i++)) | ||
res.parms.push(right) | ||
} | ||
this.log.trace("Checking client:", origin, "cb:", !!cb, "q:", query, "s:", secure, "IP:", req.connection.remoteAddress, "pub:", pub) | ||
if (user && token && pub) //User connecting with a token | ||
this.validateToken(user, token, pub, listen, payload, (valid)=>{ | ||
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect | ||
}) | ||
return clauses.join(' and ') | ||
} else if ('items' in logic) { //Logic list syntax | ||
if (!('and' in logic)) logic.and = true //Default to 'and' combiner | ||
let clauses = []; logic.items.forEach((item) => { | ||
let clause = this.buildWhere(item, res) | ||
if (clause) clauses.push(clause) | ||
else if (user && sign && date) { //User has a signature | ||
let message = JSON.stringify({ip: req.connection.remoteAddress, cookie: req.headers.cookie, userAgent: req.headers['user-agent'], date}) | ||
, now = new Date() | ||
, msgDate = new Date(date) | ||
this.log.debug("Check dates:", now, msgDate, this.maxDelta, "Time delta:", now - msgDate) | ||
if (this.maxDelta && Math.abs(now - msgDate) > this.maxDelta) | ||
cb(false, 400, 'Invalid Date Stamp') | ||
else this.validateSignature(user, sign, message, listen, payload, (valid)=>{ | ||
cb(valid, 403, 'Invalid Login') //Tell websocket whether or not to connect | ||
}) | ||
return clauses.join(logic.and ? ' and ' : ' or ') | ||
} else if ('left' in logic) { //Logic clause syntax | ||
if (logic.oper == 'nop') return null | ||
let oper = logic.oper || '=' | ||
if (logic.left === null || logic.left === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null} | ||
if (!Opers.includes(oper)) {res.error = this.error("invalid operator: " + oper, 'badOperator'); return null} | ||
if (logic.oper == 'diff') logic.oper = 'is distinct from' | ||
if (logic.oper == 'isnull' || logic.oper == 'notnull') { | ||
return Format("%s(%I %s)", logic.not ? 'not ' : '', logic.left, logic.oper) | ||
} else if (user && !secure) { | ||
Object.assign(payload, {user,listen}) //Tell later db connect our username and db listen options | ||
cb(true) //On an insecure/debug web connection | ||
} else if (logic.oper == 'true') { //LHS only | ||
return Format("%s%I", logic.not ? 'not ' : '', logic.left) | ||
} else if (logic.oper == 'in') { //LHS in array or set | ||
if (logic.entry) { //RHS is explicit | ||
let right = logic.entry | ||
, notter = logic.not ? 'not ' : '' | ||
if (Array.isArray(right)) //Map array sub-elements to strings | ||
right = right.map(el=>(Array.isArray(el) ? el.join('~') : el)) | ||
if (typeof right == 'string') right = right.split(/[ ,]+/) //Comma separated list | ||
if (Array.isArray(logic.left)) { //Matching multiple fields against array sub-elements | ||
let left = logic.left.map(el=>(Format("%I", el))).join("||'~'||") //Map LHS to tilde joined string | ||
return Format("%s(%s in (%L))", notter, left, right) | ||
} | ||
return Format("%s(%I in (%L))", notter, logic.left, right) | ||
} else if (logic.right) { //RHS is a DB field | ||
return Format("%s(%I = any(%I))", logic.not ? 'not ' : '', logic.left, logic.right) | ||
} else { | ||
res.error = this.error("invalid or null right side", 'badRight'); return null | ||
} | ||
} else { | ||
res.parms.push(logic.entry || logic.right || '') | ||
return Format("%s(%I %s $%s)", logic.not ? 'not ' : '', logic.left, oper, i++) | ||
} | ||
} else if (typeof logic == 'object') { //Compact form, each key is an = clause, anded together | ||
let clauses = []; | ||
Object.keys(logic).forEach((key) => { | ||
clauses.push(Format("%I = $%s", key, i++)) | ||
res.parms.push(logic[key]) | ||
}) | ||
return clauses.join(' and ') | ||
} | ||
res.error = this.error("mangled logic: " + logic, 'badLogic') | ||
return null | ||
} else | ||
cb(false, 401, 'No login credentials') //tell websocket not to connect | ||
} | ||
// Create a field ordering clause from a JSON structure | ||
//Service an incoming connection on a TCP socket | ||
// ----------------------------------------------------------------------------- | ||
buildOrder(order, res) { | ||
this.log.trace("Order:", order, typeof order) | ||
if (!order) return null | ||
if (!Array.isArray(order)) order = [order] | ||
let ords = []; | ||
order.forEach(el => { | ||
if (typeof el == 'object') { | ||
let col = el.field || el.column || el.columnId | ||
if (col === null || col === undefined) {res.error = this.error("invalid null or undefined field", 'badFieldName'); return null} | ||
ords.push(Format("%I %s", el.field || el.column || el.columnId, (el.asc || el.sortAsc) ? 'asc' : 'desc')) | ||
} else if (typeof el == 'number') { | ||
ords.push(Format("%s", el)) | ||
} else if (typeof el == 'string') { | ||
ords.push(Format("%I", el)) | ||
} | ||
sockConnect(ss) { | ||
this.log.debug("Wyseman socket connection") | ||
ss.on('end', () => { | ||
this.log.debug("Wyseman socket disconnect") | ||
}) | ||
return ords.length > 0 ? ords.join(', ') : null | ||
ss.on('error', err => { | ||
this.log.debug("Wyseman socket error:", err) | ||
}) | ||
ss.on('data', data => { | ||
let msg = data.toString().trim() | ||
this.log.debug("Wyseman socket data:", msg) | ||
}) | ||
} | ||
} //class Wyseman | ||
// ----------------------------------------------------------------------------- | ||
// def tables_ref(tab, refme=false) //Return tables that are referenced (pointed to) by the specified table | ||
// #Port_me //If refme true, return tables that reference the specified table | ||
// end | ||
// ----------------------------------------------------------------------------- | ||
// def columns_fk(tab, ftab) //Return the fk columns in a table and the pk columns they point to in a foreign table | ||
// #Port_me | ||
// end |
{ | ||
"name": "wyseman", | ||
"version": "1.0.18", | ||
"version": "1.0.19", | ||
"description": "PostgreSQL Schema Manager with Javascript, Ruby, TCL API", | ||
@@ -33,10 +33,11 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"base64-js": "^1.3.1", | ||
"pg": "^8.0.3", | ||
"base64-js": "^1.5.1", | ||
"node-fetch": "^2.6.1", | ||
"pg": "^8.5.1", | ||
"pg-format": "^1.0.4", | ||
"ws": "^7.2.5" | ||
"ws": "^7.4.3" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^7.1.2" | ||
"mocha": "^7.2.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
674937
72
811
5
5
+ Addednode-fetch@^2.6.1
+ Addednode-fetch@2.7.0(transitive)
+ Addedtr46@0.0.3(transitive)
+ Addedwebidl-conversions@3.0.1(transitive)
+ Addedwhatwg-url@5.0.0(transitive)
Updatedbase64-js@^1.5.1
Updatedpg@^8.5.1
Updatedws@^7.4.3