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.
@@ -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,{retrieveIgnoringWhere:true})
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 rows=[{
1744
- '#worksheet':sheet1name+'b',
1743
+ var dataSheet = {
1744
+ name: sheet1name,
1745
1745
  freezeRows: 1,
1746
1746
  freezeColumns: grid.def.primaryKey?.length ?? 0,
1747
1747
  autoWidthMax: 40,
1748
- }];
1749
- rows=rows.concat(tableRowsXLS(grid.depotsToDisplay, fieldsDef2Export));
1750
- rows.push({'#worksheet':sheet2name, autoWidthMax: 40});
1751
- // La primera fila de metadata queda en blanco, como cuando las
1752
- // celdas se escribían por dirección arrancando en la fila 2.
1753
- rows.push([]);
1754
- rows.push([{v:'table',s:'header'}, grid.def.name]);
1755
- rows.push([{v:'date' ,s:'header'}, new Date().toISOString()]);
1756
- rows.push([{v:'user' ,s:'header'}, my.config.username]);
1757
- // grid.def.allow.forEach(function(action,iAction){
1758
- // exportFileInformationWs[XLSX.utils.encode_cell({c:iAction,r:2})]={t:'s',v:action};
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=rows.concat(tableRowsXLS(fieldPropertiesDepot,fieldPropertiesDefs,1));
1771
+ metadataSheet.rows.push(...(tableRowsXLS(fieldPropertiesDepot,fieldPropertiesDefs,1)));
1772
1772
  }
1773
1773
  }
1774
1774
  xlsxNowBrowser.createXlsxBlob({
1775
- rows,
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
- retrieveIgnoringWhere: opts?opts.retrieveIgnoringWhere:false
2438
+ retrieveWithBroadWhere : opts?.retrieveWithBroadWhere ?? false
2439
2439
  }).then(function(result){
2440
2440
  grid.depotRefresh(depot,{updatedRow:result[0], sendedForUpdate:{}}, opts);
2441
2441
  })
@@ -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'
@@ -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 XLSX = require('xlsx');
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
- let isScramValid = false;
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
- needsMigration = true;
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 wb = XLSX.utils.book_new();
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
- var ws = XLSX.utils.aoa_to_sheet(rowsA);
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 new Promise((resolve,reject)=>XLSX.writeFileAsync(`dist/client/${fileName}`,wb,{},
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}]:[]),
@@ -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: 'retrieveIgnoringWhere', defaultValue:false}
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[], pick:string, retrieveIgnoringWhere:boolean}} parameters
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 defTable=be.tableStructures[tableName];
100
- if(!defTable){
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=defTable(context);
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 "+((parameters.retrieveIgnoringWhere?'true':defTable.sql.where)||(defTable.allow.select && !defTable.forInsertOnlyMode?'true':'false'))+fixedClausule.join("")+
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.11",
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.6"
79
+ "xlsx-now": "^0.2.1"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@types/big.js": "^7.0.0",