backend-plus 2.7.0-beta.11 → 2.7.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/for-client/my-tables.js +18 -18
- package/lib/backend-plus.d.ts +4 -0
- package/lib/backend-plus.js +39 -86
- package/lib/procedures-table.js +19 -6
- package/package.json +2 -2
package/for-client/my-tables.js
CHANGED
|
@@ -687,7 +687,7 @@ myOwn.tableGrid = function tableGrid(tableName, mainElement, opts){
|
|
|
687
687
|
depot.manager.displayAsDeleted(depot, force ? 'change-ff' : 'unknown');
|
|
688
688
|
if (myOwn.config.config['grid-row-retain-moved-or-deleted']) {
|
|
689
689
|
if(!depot['$refreshed']){
|
|
690
|
-
grid.retrieveRowAndRefresh(depot,{
|
|
690
|
+
grid.retrieveRowAndRefresh(depot,{retrieveWithBroadWhere:true})
|
|
691
691
|
depot['$refreshed'] = true
|
|
692
692
|
}
|
|
693
693
|
}
|
|
@@ -1740,23 +1740,23 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1740
1740
|
var excelExport = function(){
|
|
1741
1741
|
var sheet1name=grid.def.name.length>27?grid.def.name.slice(0,27)+'...':grid.def.name;
|
|
1742
1742
|
var sheet2name=grid.def.name!=="metadata"?"metadata":"meta-data";
|
|
1743
|
-
var
|
|
1744
|
-
|
|
1743
|
+
var dataSheet = {
|
|
1744
|
+
name: sheet1name,
|
|
1745
1745
|
freezeRows: 1,
|
|
1746
1746
|
freezeColumns: grid.def.primaryKey?.length ?? 0,
|
|
1747
1747
|
autoWidthMax: 40,
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1748
|
+
rows: tableRowsXLS(grid.depotsToDisplay, fieldsDef2Export)
|
|
1749
|
+
};
|
|
1750
|
+
var metadataSheet = {
|
|
1751
|
+
name:sheet2name,
|
|
1752
|
+
autoWidthMax: 40,
|
|
1753
|
+
rows: [
|
|
1754
|
+
[],
|
|
1755
|
+
[{v:'table',s:'header'}, grid.def.name],
|
|
1756
|
+
[{v:'date' ,s:'header'}, new Date().toISOString()],
|
|
1757
|
+
[{v:'user' ,s:'header'}, my.config.username],
|
|
1758
|
+
]
|
|
1759
|
+
};
|
|
1760
1760
|
if(grid.def.exportMetadata){
|
|
1761
1761
|
if(grid.def.exportMetadata.fieldProperties){
|
|
1762
1762
|
var fieldPropertiesDefs=grid.def.exportMetadata.fieldProperties.map(function(propName, i){
|
|
@@ -1768,11 +1768,11 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1768
1768
|
return {row:fieldDef};
|
|
1769
1769
|
});
|
|
1770
1770
|
extraColumns={};
|
|
1771
|
-
rows
|
|
1771
|
+
metadataSheet.rows.push(...(tableRowsXLS(fieldPropertiesDepot,fieldPropertiesDefs,1)));
|
|
1772
1772
|
}
|
|
1773
1773
|
}
|
|
1774
1774
|
xlsxNowBrowser.createXlsxBlob({
|
|
1775
|
-
|
|
1775
|
+
sheets: [dataSheet, metadataSheet],
|
|
1776
1776
|
styles:XLSX_STYLES
|
|
1777
1777
|
}).then(function(blob){
|
|
1778
1778
|
mainDiv.setAttribute("current-state", "ready");
|
|
@@ -2435,7 +2435,7 @@ myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
|
|
|
2435
2435
|
return {fieldName:fieldName, value:depot.primaryKeyValues[i]};
|
|
2436
2436
|
}),
|
|
2437
2437
|
pick:grid.def.pick,
|
|
2438
|
-
|
|
2438
|
+
retrieveWithBroadWhere : opts?.retrieveWithBroadWhere ?? false
|
|
2439
2439
|
}).then(function(result){
|
|
2440
2440
|
grid.depotRefresh(depot,{updatedRow:result[0], sendedForUpdate:{}}, opts);
|
|
2441
2441
|
})
|
package/lib/backend-plus.d.ts
CHANGED
|
@@ -284,10 +284,12 @@ export type TableDefinition = EditableDbDefinition & {
|
|
|
284
284
|
primaryKey:string[]
|
|
285
285
|
refrescable?: boolean
|
|
286
286
|
sql?:{
|
|
287
|
+
select: string[]
|
|
287
288
|
primaryKey4Delete?:string[]
|
|
288
289
|
isTable?:boolean
|
|
289
290
|
from?:string
|
|
290
291
|
where?:string
|
|
292
|
+
broadWhere?:string
|
|
291
293
|
postCreateSqls?:string
|
|
292
294
|
skipEnance?: boolean
|
|
293
295
|
isReferable?: boolean
|
|
@@ -338,6 +340,7 @@ export type TableDefinition = EditableDbDefinition & {
|
|
|
338
340
|
policy?:string
|
|
339
341
|
firstDisplayCount?:number
|
|
340
342
|
firstDisplayOverLimit?:number
|
|
343
|
+
forInsertOnlyMode?:boolean
|
|
341
344
|
description?:MarkdownDoc
|
|
342
345
|
exportJsonFieldAsColumns?:string
|
|
343
346
|
importCuidado?:boolean
|
|
@@ -350,6 +353,7 @@ export type TableDefinition = EditableDbDefinition & {
|
|
|
350
353
|
functionDef?:{
|
|
351
354
|
parameters?:ProcedureParameter[]
|
|
352
355
|
}
|
|
356
|
+
|
|
353
357
|
}
|
|
354
358
|
export type TableDefinitionInternal = RequireSome<TableDefinition,
|
|
355
359
|
'allow'|'sql'
|
package/lib/backend-plus.js
CHANGED
|
@@ -63,7 +63,37 @@ var myOwn = require('../for-client/my-things.js');
|
|
|
63
63
|
|
|
64
64
|
var typeStore = require('type-store');
|
|
65
65
|
var json4all = require('json4all');
|
|
66
|
-
var
|
|
66
|
+
var xlsxNow = require('xlsx-now/node');
|
|
67
|
+
var xlsxNowCore = require('xlsx-now');
|
|
68
|
+
var Big = require('big.js');
|
|
69
|
+
|
|
70
|
+
// Los tipos que las queries devuelven y no son valores nativos de una celda.
|
|
71
|
+
// Sin esto xlsx-now los rechaza, porque un objeto que nadie reclama no tiene
|
|
72
|
+
// una única forma correcta de escribirse.
|
|
73
|
+
var xlsxTypes = xlsxNowCore.defaultTypes;
|
|
74
|
+
// Un timestamp es una fecha: se escribe como tal y no como texto.
|
|
75
|
+
xlsxTypes = xlsxNowCore.withType(xlsxTypes, bestGlobals.Datetime, {
|
|
76
|
+
convert: (value, context) => xlsxNowCore.dateValue(new Date(value.getTime()), context)
|
|
77
|
+
});
|
|
78
|
+
// Igual que en la exportación del cliente: días, con formato de duración.
|
|
79
|
+
xlsxTypes = xlsxNowCore.withType(xlsxTypes, bestGlobals.TimeInterval, {
|
|
80
|
+
convert: (value) => ({v:value.timeInterval.ms?value.timeInterval.ms/(1000*60*60*24):null, numFmt:'[h]:mm:ss'})
|
|
81
|
+
});
|
|
82
|
+
// decimal y hugeint. Como texto cuando el double ya no los representa exacto,
|
|
83
|
+
// para no devolver un número redondeado con apariencia de exacto.
|
|
84
|
+
xlsxTypes = xlsxNowCore.withType(xlsxTypes, Big, {
|
|
85
|
+
convert: function(value){
|
|
86
|
+
var text = value.toString();
|
|
87
|
+
var asNumber = Number(text);
|
|
88
|
+
return Number.isSafeInteger(asNumber) || String(asNumber) === text
|
|
89
|
+
? {v:asNumber}
|
|
90
|
+
: {v:text, t:'inlineStr'};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// jsonb, jsona, gpoint, point y cualquier otro objeto sin tipo propio.
|
|
94
|
+
xlsxTypes = xlsxNowCore.withType(xlsxTypes, Object, {
|
|
95
|
+
convert: (value) => ({v:JSON.stringify(value), t:'inlineStr'})
|
|
96
|
+
});
|
|
67
97
|
|
|
68
98
|
var loginPlus = require('login-plus');
|
|
69
99
|
var dashDashDir=Array.prototype.indexOf.call(process.argv,'--dir')
|
|
@@ -85,9 +115,6 @@ function md5(text){
|
|
|
85
115
|
const DEFAULT_ITERATIONS = 4096;
|
|
86
116
|
const HASH_ALGORITHM = 'sha256';
|
|
87
117
|
|
|
88
|
-
// primera version de scram sha 256. El Verifier completo tenía 64 bytes.
|
|
89
|
-
const LEGACY_KEY_LENGTH = 64;
|
|
90
|
-
|
|
91
118
|
// Formato nuevo/PG-Compatible: El Client Key tiene 32 bytes (SHA-256).
|
|
92
119
|
const SCRAM_KEY_LENGTH = 32;
|
|
93
120
|
|
|
@@ -185,57 +212,6 @@ async function verifyScramPG(password, storedScramString) {
|
|
|
185
212
|
return storedKeyMatch && serverKeyMatch;
|
|
186
213
|
}
|
|
187
214
|
|
|
188
|
-
/**
|
|
189
|
-
* Verifica una contraseña contra el hash SCRAM Legacy (64 bytes).
|
|
190
|
-
* Formato Legacy: SCRAM-SHA-256$<iteraciones>:<Salt>$<Verifier(64 bytes)>
|
|
191
|
-
* @param {string} password - Contraseña en texto plano.
|
|
192
|
-
* @param {string} storedScramString - Cadena SCRAM Legacy.
|
|
193
|
-
* @returns {Promise<boolean>} True si la contraseña es válida.
|
|
194
|
-
*/
|
|
195
|
-
async function verifyScramLegacy(password, storedScramString) {
|
|
196
|
-
if (!storedScramString.startsWith('SCRAM-SHA-256$')) {
|
|
197
|
-
// No es formato SCRAM. Debe ser MD5 u otro algoritmo.
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Parsea la cadena SCRAM
|
|
202
|
-
// Ejemplo: SCRAM-SHA-256$4096:SALT_BASE64$VERIFIER_BASE64
|
|
203
|
-
const parts = storedScramString.split('$');
|
|
204
|
-
if (parts.length !== 3) {
|
|
205
|
-
throw new Error('Formato SCRAM almacenado inválido.');
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Obtiene iteraciones y salt de la segunda parte (ej: '4096:SALT_BASE64')
|
|
209
|
-
const [storedIterations, storedSalt] = parts[1].split(':');
|
|
210
|
-
const storedVerifier = parts[2];
|
|
211
|
-
|
|
212
|
-
if (!storedSalt || !storedVerifier || isNaN(parseInt(storedIterations))) {
|
|
213
|
-
throw new Error('Datos de SCRAM incompletos o malformados.');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const iterations = parseInt(storedIterations);
|
|
217
|
-
|
|
218
|
-
// Deriva la clave de la contraseña ingresada
|
|
219
|
-
const generatedVerifierBuffer = await deriveKey(
|
|
220
|
-
password,
|
|
221
|
-
storedSalt,
|
|
222
|
-
iterations,
|
|
223
|
-
LEGACY_KEY_LENGTH
|
|
224
|
-
);
|
|
225
|
-
const generatedVerifier = bufferToBase64(generatedVerifierBuffer);
|
|
226
|
-
|
|
227
|
-
// Compara de manera segura contra el Verificador Almacenado
|
|
228
|
-
const storedVerifierBuffer = Buffer.from(storedVerifier, 'base64');
|
|
229
|
-
const generatedVerifierBufferFromBase64 = Buffer.from(generatedVerifier, 'base64');
|
|
230
|
-
|
|
231
|
-
// Usa crypto.timingSafeEqual para evitar ataques de temporización
|
|
232
|
-
if (generatedVerifierBufferFromBase64.length !== storedVerifierBuffer.length) {
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return crypto.timingSafeEqual(generatedVerifierBufferFromBase64, storedVerifierBuffer);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
215
|
var dist=regexpDistCheck.test(packagejson.main)?'dist/':'';
|
|
240
216
|
|
|
241
217
|
/**
|
|
@@ -1253,7 +1229,6 @@ AppBackend.prototype.start = function start(opts){
|
|
|
1253
1229
|
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1254
1230
|
return
|
|
1255
1231
|
}else{
|
|
1256
|
-
let needsMigration = false;
|
|
1257
1232
|
const user = data.row;
|
|
1258
1233
|
if(!user[passFieldName]){
|
|
1259
1234
|
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
@@ -1261,34 +1236,20 @@ AppBackend.prototype.start = function start(opts){
|
|
|
1261
1236
|
}
|
|
1262
1237
|
const usaScramSha256 = user[passFieldName].startsWith('SCRAM-SHA-256$')
|
|
1263
1238
|
if (usaScramSha256){
|
|
1264
|
-
|
|
1265
|
-
// 1. Intento con formato PG-Compatible (Nuevo)
|
|
1266
|
-
if(await verifyScramPG(password, user[passFieldName])){
|
|
1267
|
-
isScramValid = true;
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
// 2. Intento con formato Legacy (64 bytes)
|
|
1271
|
-
else if(await verifyScramLegacy(password, user[passFieldName])){
|
|
1272
|
-
isScramValid = true;
|
|
1273
|
-
needsMigration = true;
|
|
1274
|
-
}
|
|
1275
|
-
if(!isScramValid){
|
|
1239
|
+
if(!await verifyScramPG(password, user[passFieldName])){
|
|
1276
1240
|
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1277
1241
|
return
|
|
1278
1242
|
}
|
|
1279
1243
|
}else{
|
|
1280
1244
|
if (md5(password+username.toLowerCase()) === user[passFieldName]){
|
|
1281
|
-
|
|
1245
|
+
if (be.config.log["pass-migration"]) console.log('Ejecutando migración a SCRAM...');
|
|
1246
|
+
await updatePassword({client, username, password, setUpdateDate:false, errorIfNoResult:true});
|
|
1247
|
+
if (be.config.log["pass-migration"]) console.log('Migración completada.');
|
|
1282
1248
|
}else{
|
|
1283
1249
|
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1284
1250
|
return
|
|
1285
1251
|
}
|
|
1286
1252
|
}
|
|
1287
|
-
if(needsMigration){
|
|
1288
|
-
if (be.config.log["pass-migration"]) console.log('Ejecutando migración a SCRAM...');
|
|
1289
|
-
await updatePassword({client, username, password, setUpdateDate:false, errorIfNoResult:true});
|
|
1290
|
-
if (be.config.log["pass-migration"]) console.log('Migración completada.');
|
|
1291
|
-
}
|
|
1292
1253
|
}
|
|
1293
1254
|
//continua validando
|
|
1294
1255
|
if(data.rowCount==1){
|
|
@@ -1358,9 +1319,6 @@ AppBackend.prototype.start = function start(opts){
|
|
|
1358
1319
|
if (oldPassword !== false) {
|
|
1359
1320
|
if (storedHash.startsWith('SCRAM-SHA-256$')) {//Intento SCRAM-SHA-256
|
|
1360
1321
|
ok = await verifyScramPG(oldPassword, storedHash);
|
|
1361
|
-
if (!ok) {
|
|
1362
|
-
ok = await verifyScramLegacy(oldPassword, storedHash);
|
|
1363
|
-
}
|
|
1364
1322
|
} else { //Intento MD5
|
|
1365
1323
|
const md5Hash = md5(oldPassword + username.toLowerCase());
|
|
1366
1324
|
ok = (md5Hash === storedHash);
|
|
@@ -3822,21 +3780,16 @@ AppBackend.prototype.exportacionesGenerico = async function exportacionesGeneric
|
|
|
3822
3780
|
}
|
|
3823
3781
|
if(fileName){
|
|
3824
3782
|
context.informProgress({message:'armando XLSX'})
|
|
3825
|
-
var
|
|
3826
|
-
result.map(({title, rows})=>{
|
|
3783
|
+
var sheets = result.map(({title, rows})=>{
|
|
3827
3784
|
var rowsA = rows.map(row=>Object.values(row));
|
|
3828
3785
|
if(rows[0]){
|
|
3829
|
-
rowsA.unshift(Object.keys(rows[0]));
|
|
3786
|
+
rowsA.unshift({'#line': 'array', values:Object.keys(rows[0]), s: {bold: true}});
|
|
3830
3787
|
}
|
|
3831
|
-
|
|
3832
|
-
XLSX.utils.book_append_sheet(wb, ws, title);
|
|
3833
|
-
return ws;
|
|
3788
|
+
return {name:title, rows:rowsA};
|
|
3834
3789
|
})
|
|
3835
3790
|
context.informProgress({message:`guardando XLSX: ${fileName}`})
|
|
3836
3791
|
await bestGlobals.sleep(100);
|
|
3837
|
-
await
|
|
3838
|
-
(err)=>err?reject(err):resolve()
|
|
3839
|
-
))
|
|
3792
|
+
await xlsxNow.writeXlsxFile(`dist/client/${fileName}`, {sheets, types:xlsxTypes, autoWidthMax:50, freezeRows:1});
|
|
3840
3793
|
}
|
|
3841
3794
|
return [
|
|
3842
3795
|
...(fileName?[{url:fileName, label:csvFileName?'xlsx de control':fileName}]:[]),
|
package/lib/procedures-table.js
CHANGED
|
@@ -10,6 +10,7 @@ var likeAr=require('like-ar');
|
|
|
10
10
|
const f = require('session-file-store');
|
|
11
11
|
const { expected } = require('cast-error');
|
|
12
12
|
const bestGlobals = require('best-globals');
|
|
13
|
+
const { AppBackend } = require('backend-plus');
|
|
13
14
|
|
|
14
15
|
const PANIC_IMPORT = true;
|
|
15
16
|
|
|
@@ -85,26 +86,29 @@ ProcedureTables = [
|
|
|
85
86
|
{name: 'fixedFields', defaultValue:[]},
|
|
86
87
|
{name: 'paramfun', defaultValue:[]},
|
|
87
88
|
{name: 'pick', defaultValue:'', encoding:'plain'},
|
|
88
|
-
{name: '
|
|
89
|
+
{name: 'retrieveWithBroadWhere', defaultValue:false}
|
|
89
90
|
],
|
|
90
91
|
coreFunction:
|
|
91
92
|
/**
|
|
92
93
|
*
|
|
93
94
|
* @param {*} context
|
|
94
|
-
* @param {{table:string, fixedFields:{fieldName:string, value:any, range?:string, until?:string}[], paramfun:string
|
|
95
|
+
* @param {{table:string, fixedFields:{fieldName:string, value:any, range?:string, until?:string}[], paramfun:Record<string, string>, pick:string, retrieveWithBroadWhere:boolean}} parameters
|
|
95
96
|
*/
|
|
96
97
|
async function tableDatum(context, parameters){
|
|
98
|
+
/** @type {AppBackend} */
|
|
97
99
|
var be=context.be;
|
|
98
100
|
var tableName=parameters.table;
|
|
99
|
-
var
|
|
100
|
-
if(!
|
|
101
|
+
var defTableFun=be.tableStructures[tableName];
|
|
102
|
+
if(!defTableFun){
|
|
101
103
|
throw new Error('no table def for '+tableName+' (in table_data)')
|
|
102
104
|
}
|
|
103
|
-
defTable=
|
|
105
|
+
var defTable=defTableFun(context);
|
|
104
106
|
/** @type {string} */
|
|
105
107
|
var sql;
|
|
106
108
|
var queryValues=[];
|
|
109
|
+
/** @type {string[]} */
|
|
107
110
|
var specialFixedClause=[];
|
|
111
|
+
/** @type {string[]} */
|
|
108
112
|
var fixedClausule=[];
|
|
109
113
|
if(defTable.functionDef){
|
|
110
114
|
parameters.fixedFields.forEach(function(pair, iPair){
|
|
@@ -155,10 +159,19 @@ ProcedureTables = [
|
|
|
155
159
|
fixedClausule.push(exprClausule)
|
|
156
160
|
}
|
|
157
161
|
});
|
|
162
|
+
|
|
163
|
+
const getBaseWhere = (defTable, params) => {
|
|
164
|
+
if (params?.retrieveWithBroadWhere && defTable.sql?.broadWhere) return defTable.sql.broadWhere;
|
|
165
|
+
if (params?.retrieveWithBroadWhere) console.warn("No broadWhere found for retrieveWithBroadWhere. Using default configuration.");
|
|
166
|
+
return defTable.sql?.where || (defTable.allow?.select && !defTable.forInsertOnlyMode ? 'true' : 'false');
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const whereCondition = getBaseWhere(defTable, parameters) + fixedClausule.join("");
|
|
170
|
+
|
|
158
171
|
/** @type {string} */
|
|
159
172
|
var sql="SELECT "+[...defTable.sql.select].join(', ')+
|
|
160
173
|
"\n FROM "+defTable.sql.from+
|
|
161
|
-
"\n WHERE "+
|
|
174
|
+
"\n WHERE "+whereCondition+
|
|
162
175
|
// " ORDER BY "+defTable.primaryKey.map(be.db.quoteIdent.bind(be.db)).join(',')
|
|
163
176
|
"\n ORDER BY "+(defTable.sql.orderBy||defTable.primaryKey).map(function(fieldName){ return be.db.quoteIdent(fieldName); }).join(',')
|
|
164
177
|
if(specialFixedClause.length){
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend-plus",
|
|
3
3
|
"description": "Backend for the anti Pareto rule",
|
|
4
|
-
"version": "2.7.0-beta.
|
|
4
|
+
"version": "2.7.0-beta.13",
|
|
5
5
|
"author": "Codenautas <codenautas@googlegroups.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "codenautas/backend-plus",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"type-store": "^0.6.0",
|
|
77
77
|
"typed-controls": "^0.12.7",
|
|
78
78
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz",
|
|
79
|
-
"xlsx-now": "^0.1
|
|
79
|
+
"xlsx-now": "^0.2.1"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@types/big.js": "^7.0.0",
|