backend-plus 2.5.2-betha.9 → 2.5.2

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.
@@ -9,6 +9,7 @@ html(lang=lang)
9
9
  .iconostd{
10
10
  display: none
11
11
  }
12
+ meta(name='viewport' content="width=device-width, user-scalable=no")
12
13
  body
13
14
  form(id="chPassForm", action="chpass", method="post")
14
15
  table.chpasstable
@@ -59,7 +60,8 @@ html(lang=lang)
59
60
  td#resultDiff
60
61
  tr
61
62
  td
62
- td
63
+ td.actions
64
+ a(href="./menu")=msgs.chpass.cancel
63
65
  input(type="submit", value=msgs.chpass.button)
64
66
  each errorLine in (flash.error||[])
65
67
  tr(style={color:'red'})
@@ -9,6 +9,7 @@ html(lang=lang)
9
9
  .iconostd{
10
10
  display: none
11
11
  }
12
+ meta(name='viewport' content="width=device-width, user-scalable=no")
12
13
  body
13
14
  form(id="loginForm", action="login", method="post")
14
15
  table.logintable
@@ -199,6 +199,7 @@ myOwn.wScreens.proc.result={
199
199
  }else{
200
200
  divResult.textContent = result;
201
201
  divResult.style.backgroundColor = '#9FA';
202
+ divResult.setAttribute('result','success');
202
203
  }
203
204
  },
204
205
  showGrid:function(result, divResult){
@@ -207,6 +208,8 @@ myOwn.wScreens.proc.result={
207
208
  showError:function(err, divResult){
208
209
  divResult.textContent = err.message;
209
210
  divResult.style.backgroundColor = 'orange';
211
+ divResult.setAttribute('result','error');
212
+
210
213
  },
211
214
  showDownloadUrl:function(result, divResult){
212
215
  divResult.innerHTML='';
@@ -483,6 +486,7 @@ myOwn.displayMainMenu = function(addrParams){
483
486
  if(mtv){
484
487
  if(location.href.match(/^.{0,8}localhost/)){
485
488
  mtv.style.backgroundImage='repeating-linear-gradient(-45deg, #FF8, #FF8 8px, #DDD 8px, #DDD 16px)'
489
+ mtv.setAttribute('dev-mode',true);
486
490
  mtv.title = 'Warning! Using system in localhost!'
487
491
  }
488
492
  }
@@ -903,7 +903,7 @@ myOwn.ActionColumnGrid.prototype.td = function td(depot){
903
903
  actionNamesList.forEach(function(actionName){
904
904
  var actionDef = my.tableAction[actionName];
905
905
  if(grid.def.allow[actionName] && depot.allow[actionName] !== false){
906
- var buttonAction=html.button({class:'table-button', "skip-enter":true}, [
906
+ var buttonAction=html.button({class:'table-button', "skip-enter":true, "bp-action":actionDef.alt}, [
907
907
  html.img({src:actionDef.img, alt:actionDef.alt, title:my.messages[actionDef.titleMsg]})
908
908
  ]).create();
909
909
  thActions.appendChild(buttonAction);
@@ -1983,7 +1983,7 @@ myOwn.TableGrid.prototype.prepareGrid = function prepareGrid(){
1983
1983
  });
1984
1984
  }
1985
1985
  if(grid.def.allow.insert && !grid.def.forInsertOnlyMode){
1986
- buttonInsert=html.button({class:'table-button', "enter-clicks":true}, [
1986
+ buttonInsert=html.button({class:'table-button', "enter-clicks":true, "bp-action": "INS"}, [
1987
1987
  html.img({
1988
1988
  src:my.path.img+'insert.png',
1989
1989
  alt:'INS',
@@ -2203,6 +2203,9 @@ myOwn.specialDefaultValue={
2203
2203
  return belowDepot.row[fieldName]?belowDepot.row[fieldName]+1:(
2204
2204
  aboveDepot.row[fieldName]?aboveDepot.row[fieldName]-1:1
2205
2205
  );
2206
+ },
2207
+ current_user: function specialDefaultValueCurrentUser(){
2208
+ return my.config.username;
2206
2209
  }
2207
2210
  }
2208
2211
 
@@ -2309,7 +2312,7 @@ myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
2309
2312
  grid.updateRowData = function updateRowData(depot, skipUpdateStatus){
2310
2313
  var grid = this;
2311
2314
  var forInsert = false; // not define how to detect
2312
- var tr = depot;
2315
+ var tr = depot.tr;
2313
2316
  grid.setRowStyle(depot,depot.row, skipUpdateStatus);
2314
2317
  grid.def.fields.filter(function(fieldDef){
2315
2318
  return fieldDef.visible;
@@ -2341,6 +2344,12 @@ myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
2341
2344
  depot.clientSidePrepared=true;
2342
2345
  grid.my.clientSides[grid.def.clientSide].update(depot);
2343
2346
  }
2347
+ if (depot.allow.update) {
2348
+ tr.setAttribute('can-update','yes')
2349
+ }
2350
+ if (depot.allow.delete) {
2351
+ tr.setAttribute('can-delete','yes')
2352
+ }
2344
2353
  };
2345
2354
  var saveRow = function(depot, opts){
2346
2355
  if(!('saving' in depot)){
@@ -2490,7 +2499,7 @@ myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
2490
2499
  }
2491
2500
  }
2492
2501
  }
2493
- }else if(!/^\$allow\./.test(fieldName)){
2502
+ }else if(/^\$allow\./.test(fieldName)){
2494
2503
  depot.allow[fieldName.replace(/^\$allow\./,'')] = retrievedRow[fieldName];
2495
2504
  }
2496
2505
  }
@@ -2708,7 +2717,7 @@ myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
2708
2717
  if(iRow<depotsToDisplay.length){
2709
2718
  var addButtonRest = function addButtonRest(toNextRowNumber){
2710
2719
  var createRestButtonInto = function(domElement){
2711
- var buttonRest=html.button({class:'foot-info', "enter-clicks":true},"+..."+toNextRowNumber).create();
2720
+ var buttonRest=html.button({class:'foot-info', "enter-clicks":true, "all-rows-displayed": "no"},"+..."+toNextRowNumber).create();
2712
2721
  domElement.appendChild(html.span(' ').create());
2713
2722
  domElement.appendChild(buttonRest);
2714
2723
  return buttonRest;
@@ -2738,6 +2747,7 @@ myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
2738
2747
  if(grid.dom.buttonInsert){
2739
2748
  grid.dom.buttonInsert.style.visibility='visible';
2740
2749
  }
2750
+ grid.dom.headInfo.rowCount.setAttribute("all-rows-displayed", "yes")
2741
2751
  }
2742
2752
  };
2743
2753
  var linesToDisplay=(
@@ -322,6 +322,7 @@ myOwn.insertRow = function insertRow(where){
322
322
  where.smooth={};
323
323
  }
324
324
  var trDummy = section.insertRow(iRow);
325
+ trDummy.setAttribute('dummy','inserting')
325
326
  tr.style.display='none';
326
327
  for(var i=0; i<(where.smooth.colCount||1); i++){
327
328
  var cell=trDummy.insertCell(-1);
@@ -56,6 +56,7 @@ export interface ProcedureDef<T = any> {
56
56
  forExport?:{
57
57
  fileName?:string
58
58
  csvFileName?:string
59
+ generarInmediato?:boolean
59
60
  }
60
61
  }
61
62
 
@@ -175,11 +176,11 @@ export type SequenceDefinition = {
175
176
  }
176
177
  export type SequenceMadMaxDefinition = {
177
178
  madMax: string[] // grouping of mad max sequences
178
- firstValue: number
179
+ firstValue?: number
179
180
  }
180
181
  export type ExportMetadataDefinition={ /* TODO: define */ }
181
182
  export type PostInputOptions='upperSpanish' | 'upperWithoutDiacritics' | 'parseDecimal'
182
- export type FieldDefinition = EditableDbDefinition & {
183
+ export interface FieldDefinition extends EditableDbDefinition {
183
184
  name:string
184
185
  typeName:PgKnownTypes|'ARRAY:text'
185
186
  label?:string
@@ -188,8 +189,9 @@ export type FieldDefinition = EditableDbDefinition & {
188
189
  dbNullable?:boolean /* dbNullable === false is not nullabla at DB level, but not at CLIENT LEVEL */
189
190
  defaultValue?:any
190
191
  defaultDbValue?:PgKnownDbValues|string
192
+ specialDefaultValue?:string /* keyof myOwn.specialDefaultValues and/or keyof AppBackend.prototype.specialSqlDefaultExpressions */
191
193
  clientSide?:string /* keyof: myOwn.clientSides */
192
- isName?:boolean
194
+ isName?:boolean|'known' /* is a name but it is a well known name (because the user uses it to thier code or because the code is enugh expresive)
193
195
  isPk?:number /* internal: pos in the primaryKey array */
194
196
  serverSide?:boolean /* default:!clientSide if the value is retrived from the database */
195
197
  inTable?:boolean /* default:!clientSide && !sql.fields[...].expr. Is a real fisical field in the table */
@@ -204,7 +206,6 @@ export type FieldDefinition = EditableDbDefinition & {
204
206
  references?:string /* table name */
205
207
  referencesField?:string
206
208
  aggregate?:'avg'|'sum'|'count'|'min'|'max'|'countTrue' /* keyof myOwn.TableAggregates */
207
- specialDefaultValue?:string /* keyof myOwn.specialDefaultValues
208
209
  defaultForOtherFields?:boolean /* the field that stores the "other fields" of a flexible imported table */
209
210
  specialValueWhenInsert?:string
210
211
  exportMetadata?:ExportMetadataDefinition
@@ -222,22 +223,9 @@ export type FieldDefinition = EditableDbDefinition & {
222
223
  alwaysShow?:boolean /* show when appears in fixed fields */
223
224
  suggestingKeys?:string[]
224
225
  postInput?:PostInputOptions
225
- } & (
226
- {
227
- sequence?: undefined
228
- nullable?: boolean
229
- editable?: boolean
230
- } | {
231
- sequence: SequenceDefinition
232
- nullable: true
233
- editable: false
234
- } | {
235
- sequence: SequenceMadMaxDefinition
236
- nullable: true
237
- editable: boolean
238
- }
239
- );
240
- export type EditableDbDefinition = {
226
+ sequence?: SequenceDefinition|SequenceMadMaxDefinition
227
+ }
228
+ export interface EditableDbDefinition {
241
229
  editable?:boolean
242
230
  allow?:{
243
231
  update?:boolean
@@ -254,13 +242,15 @@ export type FieldsForConnect = (string | {source:string, target:string})[]
254
242
  export type FieldsForConnectDetailTable = (string | {source:string, target:string, nullMeansAll?:boolean} | {value:any, target:string})[]
255
243
 
256
244
  export type FkActions = 'no action'|'restrict'|'cascade'|'set null'|'set default';
257
- export type ForeignKey = {
245
+ export interface ForeignKey {
258
246
  references:string,
259
247
  fields:FieldsForConnect,
260
248
  onUpdate?: FkActions,
261
249
  onDelete?: FkActions,
262
250
  displayAllFields?: boolean,
263
- alias?:string,
251
+ alias?:string,
252
+ abr?:string,
253
+ label?:string,
264
254
  displayFields?:string[],
265
255
  consName?:string,
266
256
  initiallyDeferred?:boolean
@@ -409,7 +399,9 @@ export interface AppConfigServer
409
399
  "kill-9": string // a way to kill from URL with a token
410
400
  bitacoraSchema: string
411
401
  bitacoraTableName: string
412
- allowedHosts:string[] //API allowed hosts
402
+ useCors: boolean //habilita Cross-Origin Resource Sharing
403
+ allowedHosts:string[] //determina API allowed hosts (necesita habilitar useCors)
404
+ policy?:string
413
405
  }
414
406
  export interface AppConfigDb
415
407
  {
@@ -429,6 +421,7 @@ export interface AppConfigLogin
429
421
  {
430
422
  schema: string // schema of the user table
431
423
  table: string // user table
424
+ from: string // complete expression to get table or join where get the user
432
425
  userFieldname: string // fieldname in user table that stores the user name
433
426
  passFieldname: string // fieldname in user table that stores the password hash
434
427
  rolFieldname: string // fieldname in user table that stores the rol
@@ -445,6 +438,9 @@ export interface AppConfigLogin
445
438
  store:{
446
439
  module: string
447
440
  }
441
+ successRedirect: string // where to redirect after login
442
+ failureRedirect: string // where to redirect after login failure
443
+ failedLoginUrlPath: string // where to redirect after a failed login
448
444
  }
449
445
  forget: { // forget password configurations:
450
446
  urlPath: string // url sent by mail. default: `/new-pass`
@@ -514,6 +510,7 @@ export interface AppConfig {
514
510
  results: boolean // if query results must be included in full db logs
515
511
  }
516
512
  session: boolean // if all session activity must be logged
513
+ "pass-migration"?: boolean
517
514
  }
518
515
  devel: {
519
516
  delay: number // msec avg random delay in API responses (to emulate slow nets)
@@ -557,6 +554,7 @@ export class AppBackend{
557
554
  dbUserNameExpr:string
558
555
  dbUserRolExpr:string
559
556
  specialValueWhenInsert:{[k:string]:(context:ProcedureContext, defField:FieldDefinition, parameters:object)=>any}
557
+ specialSqlDefaultExpressions:Record<string, string>
560
558
  clearCaches():void
561
559
  start(opts?: StartOptions):Promise<void>
562
560
  getTables():TableItemDef[]
@@ -595,7 +593,7 @@ export class AppBackend{
595
593
  messages:Record<LangId,Record<string, string>>
596
594
  }
597
595
  shutdownCallbackListAdd(param:{message:string, fun:()=>Promise<void>}):void
598
- shutdownBackend():Promise<void>
596
+ shutdownBackend(opts?:{skipTurnOff?:boolean, onlyTurnOff?:boolean}):Promise<void>
599
597
  setLog(opts:{until:string, results?:boolean}):void
600
598
  getDataDumpTransformations(rawData:string):Promise<{rawData:string, prepareTransformationSql:string[], endTransformationSql:string[]}>
601
599
  }
@@ -83,7 +83,159 @@ function md5(text){
83
83
  return crypto.createHash('md5').update(text).digest('hex');
84
84
  }
85
85
 
86
- //
86
+ const DEFAULT_ITERATIONS = 4096;
87
+ const HASH_ALGORITHM = 'sha256';
88
+
89
+ // primera version de scram sha 256. El Verifier completo tenía 64 bytes.
90
+ const LEGACY_KEY_LENGTH = 64;
91
+
92
+ // Formato nuevo/PG-Compatible: El Client Key tiene 32 bytes (SHA-256).
93
+ const SCRAM_KEY_LENGTH = 32;
94
+
95
+ const bufferToBase64 = (buffer) => buffer.toString('base64');
96
+
97
+ /**
98
+ * Función central para PBKDF2 (Acepta KEY_LEN para compatibilidad).
99
+ * Devuelve la clave derivada con la longitud especificada.
100
+ */
101
+ async function deriveKey(password, saltBuffer, iterations, keyLength) {
102
+ return new Promise((resolve, reject) => {
103
+ crypto.pbkdf2(
104
+ password,
105
+ saltBuffer,
106
+ iterations,
107
+ keyLength,
108
+ HASH_ALGORITHM,
109
+ (err, derivedKey) => {
110
+ if (err) return reject(err);
111
+ resolve(derivedKey);
112
+ }
113
+ );
114
+ });
115
+ }
116
+
117
+ async function generateScramVerifier(password) {
118
+ const saltBuffer = crypto.randomBytes(16);
119
+ const saltBase64 = bufferToBase64(saltBuffer);
120
+
121
+ const Hi = await deriveKey(
122
+ password,
123
+ saltBuffer,
124
+ DEFAULT_ITERATIONS,
125
+ SCRAM_KEY_LENGTH
126
+ );
127
+ const clientKey = crypto.createHmac('sha256', Hi).update('Client Key').digest();
128
+ const serverKey = crypto.createHmac('sha256', Hi).update('Server Key').digest();
129
+
130
+ const storedKey = crypto.createHash('sha256').update(clientKey).digest();
131
+
132
+ const storedKeyBase64 = bufferToBase64(storedKey);
133
+ const serverKeyBase64 = bufferToBase64(serverKey);
134
+
135
+ return `SCRAM-SHA-256$${DEFAULT_ITERATIONS}:${saltBase64}$${storedKeyBase64}:${serverKeyBase64}`;
136
+ }
137
+
138
+ /**
139
+ * Verifica una contraseña contra el hash SCRAM PG-Compatible.
140
+ * Formato PG: SCRAM-SHA-256$<iteraciones>:<Salt>$<StoredKey>:<ServerKey>
141
+ * @param {string} password - Contraseña en texto plano.
142
+ * @param {string} storedScramString - Cadena SCRAM de PostgreSQL.
143
+ * @returns {Promise<boolean>} True si la contraseña es válida.
144
+ */
145
+ async function verifyScramPG(password, storedScramString) {
146
+ if (!storedScramString.startsWith('SCRAM-SHA-256$')) {
147
+ return false;
148
+ }
149
+
150
+ const parts = storedScramString.split('$');
151
+ if (parts.length !== 3) {
152
+ return false;
153
+ }
154
+
155
+ const [storedIterations, saltBase64] = parts[1].split(':');
156
+ const [storedKeyBase64, serverKeyBase64] = parts[2].split(':'); // Asume StoredKey y ServerKey
157
+
158
+ if (!saltBase64 || !storedKeyBase64 || !serverKeyBase64 || isNaN(parseInt(storedIterations))) {
159
+ return false;
160
+ }
161
+
162
+ const iterations = parseInt(storedIterations);
163
+ const saltBuffer = Buffer.from(saltBase64, 'base64');
164
+ const storedKeyBuffer = Buffer.from(storedKeyBase64, 'base64');
165
+ const serverKeyBuffer = Buffer.from(serverKeyBase64, 'base64');
166
+
167
+ const Hi = await deriveKey(
168
+ password,
169
+ saltBuffer,
170
+ iterations,
171
+ SCRAM_KEY_LENGTH
172
+ );
173
+
174
+ const clientKey = crypto.createHmac(HASH_ALGORITHM, Hi)
175
+ .update('Client Key')
176
+ .digest();
177
+
178
+ const serverKeyActual = crypto.createHmac(HASH_ALGORITHM, Hi)
179
+ .update('Server Key')
180
+ .digest();
181
+
182
+ const storedKeyActual = crypto.createHash('sha256').update(clientKey).digest();
183
+
184
+ const storedKeyMatch = crypto.timingSafeEqual(storedKeyActual, storedKeyBuffer);
185
+ const serverKeyMatch = crypto.timingSafeEqual(serverKeyActual, serverKeyBuffer);
186
+ return storedKeyMatch && serverKeyMatch;
187
+ }
188
+
189
+ /**
190
+ * Verifica una contraseña contra el hash SCRAM Legacy (64 bytes).
191
+ * Formato Legacy: SCRAM-SHA-256$<iteraciones>:<Salt>$<Verifier(64 bytes)>
192
+ * @param {string} password - Contraseña en texto plano.
193
+ * @param {string} storedScramString - Cadena SCRAM Legacy.
194
+ * @returns {Promise<boolean>} True si la contraseña es válida.
195
+ */
196
+ async function verifyScramLegacy(password, storedScramString) {
197
+ if (!storedScramString.startsWith('SCRAM-SHA-256$')) {
198
+ // No es formato SCRAM. Debe ser MD5 u otro algoritmo.
199
+ return false;
200
+ }
201
+
202
+ // Parsea la cadena SCRAM
203
+ // Ejemplo: SCRAM-SHA-256$4096:SALT_BASE64$VERIFIER_BASE64
204
+ const parts = storedScramString.split('$');
205
+ if (parts.length !== 3) {
206
+ throw new Error('Formato SCRAM almacenado inválido.');
207
+ }
208
+
209
+ // Obtiene iteraciones y salt de la segunda parte (ej: '4096:SALT_BASE64')
210
+ const [storedIterations, storedSalt] = parts[1].split(':');
211
+ const storedVerifier = parts[2];
212
+
213
+ if (!storedSalt || !storedVerifier || isNaN(parseInt(storedIterations))) {
214
+ throw new Error('Datos de SCRAM incompletos o malformados.');
215
+ }
216
+
217
+ const iterations = parseInt(storedIterations);
218
+
219
+ // Deriva la clave de la contraseña ingresada
220
+ const generatedVerifierBuffer = await deriveKey(
221
+ password,
222
+ storedSalt,
223
+ iterations,
224
+ LEGACY_KEY_LENGTH
225
+ );
226
+ const generatedVerifier = bufferToBase64(generatedVerifierBuffer);
227
+
228
+ // Compara de manera segura contra el Verificador Almacenado
229
+ const storedVerifierBuffer = Buffer.from(storedVerifier, 'base64');
230
+ const generatedVerifierBufferFromBase64 = Buffer.from(generatedVerifier, 'base64');
231
+
232
+ // Usa crypto.timingSafeEqual para evitar ataques de temporización
233
+ if (generatedVerifierBufferFromBase64.length !== storedVerifierBuffer.length) {
234
+ return false;
235
+ }
236
+
237
+ return crypto.timingSafeEqual(generatedVerifierBufferFromBase64, storedVerifierBuffer);
238
+ }
87
239
 
88
240
  var dist=regexpDistCheck.test(packagejson.main)?'dist/':'';
89
241
 
@@ -236,12 +388,13 @@ AppBackend.prototype.i18n.messages.en={
236
388
  button: "Sign In"
237
389
  },
238
390
  chpass:{
239
- title: 'change password',
240
- username: 'Username',
241
- oldPassword:'Old password',
242
- newPassword:'New password',
243
- repPassword:'Reenter password',
244
- button:'Change',
391
+ title: 'change password',
392
+ username: 'Username',
393
+ oldPassword:'Old password',
394
+ newPassword:'New password',
395
+ repPassword:'Reenter password',
396
+ button:'Change',
397
+ cancel: 'Cancel'
245
398
  },
246
399
  fileUploaded:'file uploaded',
247
400
  unkownExt:'unkown file extension'
@@ -316,12 +469,13 @@ AppBackend.prototype.i18n.messages.es={
316
469
  },
317
470
  logged:{
318
471
  chpass:{
319
- title: 'cambio de clave',
320
- username: 'usuario',
321
- oldPassword:'clave anterior',
322
- newPassword:'nueva clave',
323
- repPassword:'repetir nueva clave',
324
- button:'Cambiar',
472
+ title: 'cambio de clave',
473
+ username: 'usuario',
474
+ oldPassword:'clave anterior',
475
+ newPassword:'nueva clave',
476
+ repPassword:'repetir nueva clave',
477
+ button:'Cambiar',
478
+ cancel: 'Cancelar'
325
479
  },
326
480
  fileUploaded:'archivo subido',
327
481
  unkownExt:'extensión de archivo desconocida'
@@ -522,14 +676,14 @@ AppBackend.prototype.shutdownCallbackListAdd = function(messageFun){
522
676
 
523
677
 
524
678
  AppBackend.prototype._Browsers = {
525
- Edge: {short:'Ed' , minVer:14 , polly:true},
679
+ Edge: {short:'Ed' , minVer:14 , polly:117},
526
680
  Konqueror: {short:'Kq' , minVer:null, polly:true},
527
- Chromium: {short:'Chmm', minVer:49 , polly:58 },
528
- Chrome: {short:'Ch' , minVer:49 , polly:58 },
529
- Safari: {short:'Sf' , minVer:9 , polly:9 },
681
+ Chromium: {short:'Chmm', minVer:49 , polly:117},
682
+ Chrome: {short:'Ch' , minVer:49 , polly:117},
683
+ Safari: {short:'Sf' , minVer:9 , polly:17},
530
684
  IE: {short:'IE' , minVer:11 , polly:true},
531
685
  Opera: {short:'Op' , minVer:null, polly:true},
532
- Firefox: {short:'FF' , minVer:52 , polly:52 },
686
+ Firefox: {short:'FF' , minVer:52 , polly:119},
533
687
  };
534
688
 
535
689
  var fontExts = [ 'jfproj', 'ttf', 'pfa', 'woff', 'woff2', 'fnt', 'fot', 'otf', 'odttf', 'fon']
@@ -643,33 +797,42 @@ AppBackend.prototype.start = function start(opts){
643
797
  // @ts-ignore : only for testing */
644
798
  this.getMainApp = function getMainApp(){ return mainApp; };
645
799
  }
646
- this.shutdownBackend = async function shutdownBackend(){
647
- console.log('shooting down:');
648
- var waitFor = [
649
- new Promise(function(resolve,reject){
650
- console.log('*','express server');
651
- be.server.close(/** @param {Error} err */function(err){
652
- if(err){
653
- console.log('*', err)
654
- reject(err);
655
- }else{
656
- console.log('*','express server done!')
657
- resolve();
658
- }
659
- });
660
- }),
661
- ...(be.shutdownCallbackList.map(x => (async function(){
662
- console.log('shut:', x.message)
663
- await x.fun()
664
- console.log('done:', x.message)
665
- })()))
666
- ];
667
- console.log('*', 'waiting for all')
668
- await Promise.all(waitFor);
669
- console.log('*', 'all done')
670
- mainApp = null;
671
- console.log('* logWhy',logWhy)
672
- logWhy && logWhy();
800
+ this.shutdownBackend = async function shutdownBackend(opts){
801
+ var shootingDown = opts == null || !opts.onlyTurnOff;
802
+ var turningOff = opts == null || !opts.skipTurnOff;
803
+ if(!shootingDown){
804
+ console.log('shooting down:');
805
+ var waitFor = [
806
+ new Promise(function(resolve,reject){
807
+ console.log('*','express server');
808
+ be.server.close(/** @param {Error} err */function(err){
809
+ if(err){
810
+ console.log('*', err)
811
+ reject(err);
812
+ }else{
813
+ console.log('*','express server done!')
814
+ resolve();
815
+ }
816
+ });
817
+ }),
818
+ ...(be.shutdownCallbackList.map(x => (async function(){
819
+ console.log('shut:', x.message)
820
+ await x.fun()
821
+ console.log('done:', x.message)
822
+ })())),
823
+ ];
824
+ console.log('*', 'waiting for all')
825
+ await Promise.all(waitFor);
826
+ console.log('*', 'all done')
827
+ mainApp = null;
828
+ }
829
+ if(turningOff){
830
+ console.log('turning off:');
831
+ await pg.shutdown();
832
+ console.log('pg shutdown done!');
833
+ console.log('* logWhy',logWhy)
834
+ logWhy && logWhy();
835
+ }
673
836
  };
674
837
  return Promise.resolve().then(function(){
675
838
  var configList=be.configList();
@@ -705,6 +868,9 @@ AppBackend.prototype.start = function start(opts){
705
868
  }
706
869
  var storeModule = be.sessionStores[sessionStoreName];
707
870
  be.config.login.plus.store.module = storeModule;
871
+ }else{
872
+ console.error('###################### DEPRECATED #####################')
873
+ console.error('LACK OF server.session-store')
708
874
  }
709
875
  be.config.install.dump.db.owner=coalesce(be.config.db.owner,be.config.install.dump.db.owner,be.config.db.user);
710
876
  be.config.install.dump.db["owner4special-scripts"]=coalesce(be.config.install.dump.db["owner4special-scripts"],be.config.install.dump.db.owner)
@@ -802,7 +968,7 @@ AppBackend.prototype.start = function start(opts){
802
968
  }
803
969
  }).then(function(client){
804
970
  if(username){
805
- return client.query(`SELECT set_app_user(${be.db.quoteLiteral(username)})`).execute().then(function(){
971
+ return client.query(`CALL set_app_user(${be.db.quoteLiteral(username)})`).execute().then(function(){
806
972
  return client;
807
973
  });
808
974
  }else{
@@ -904,18 +1070,23 @@ AppBackend.prototype.start = function start(opts){
904
1070
  }).then(async function(){
905
1071
  mainApp = express();
906
1072
  //mainApp.use(cookieParser());
907
- const whitelist = ['localhost'].concat(be.config.server.allowedHosts||[]); // Agrega aquí los orígenes de tus aplicaciones
908
- const corsOptions = {
909
- origin: function (origin, callback) {
910
- if (whitelist.some((element)=>origin?.includes(element)) || !origin){
911
- callback(null, true);
912
- }else{
913
- callback(new Error('Not allowed by CORS'));
914
- }
915
- },
916
- credentials: true
917
- };
918
- mainApp.use(cors(corsOptions));
1073
+ if(be.config.server.allowedHosts){
1074
+ if(!Array.isArray(be.config.server.allowedHosts)){
1075
+ throw Error('allowedHosts must be an Array')
1076
+ }
1077
+ const whitelist = ['localhost'].concat(be.config.server.allowedHosts);
1078
+ const corsOptions = {
1079
+ origin: function (origin, callback) {
1080
+ if (whitelist.some((element)=>origin?.includes(element)) || !origin){
1081
+ callback(null, true);
1082
+ }else{
1083
+ callback(new Error('Not allowed by CORS'));
1084
+ }
1085
+ },
1086
+ credentials: true
1087
+ };
1088
+ mainApp.use(cors(corsOptions));
1089
+ }
919
1090
  mainApp.use(bodyParser.urlencoded({extended:true, limit: '50mb'}));
920
1091
  mainApp.use(function(req,res,next){
921
1092
  if((req.headers['content-type']||'').match(/^multipart\/form-data/)){
@@ -932,8 +1103,11 @@ AppBackend.prototype.start = function start(opts){
932
1103
  });
933
1104
  if(be.config.log.req){
934
1105
  mainApp.use(function(req,res,next){
935
- if(!/keep-alive.json$/.test(req.originalUrl) || be.config.log.req['keep-alive']){
936
- console.log('REQ',req.method,req.protocol,req.hostname,req.originalUrl,'from:',req.ip);
1106
+ if(
1107
+ (!/keep-alive.json$/.test(req.originalUrl) || be.config?.log?.req?.['keep-alive']) &&
1108
+ (!/(.png|.jpg|.js|.jsx|.css|.html)$/.test(req.originalUrl) || be.config?.log?.req?.['all'])
1109
+ ){
1110
+ console.log('REQ',req.method,req.protocol,req.hostname,req.originalUrl,'from:',req.ip,req.query);
937
1111
  }
938
1112
  next();
939
1113
  });
@@ -1008,41 +1182,110 @@ AppBackend.prototype.start = function start(opts){
1008
1182
  mainApp.loginPlusManager.closeManager();
1009
1183
  }
1010
1184
  });
1011
- be.shutdownCallbackListAdd({
1012
- message:'pg',
1013
- fun:function(){
1014
- pg.shutdown();
1185
+ const updatePassword = async ({client, username, password, setUpdateDate, errorIfNoResult}) => {
1186
+ const {table, passFieldName, userFieldName, passUpdatedAtFieldName, passAlgorithmFieldName, schema} = be.config.login;
1187
+ const hashPass = await generateScramVerifier(password);
1188
+ let params = [username, hashPass];
1189
+ let setters = [`${be.db.quoteIdent(passFieldName)} = $2`];
1190
+ if(passAlgorithmFieldName){
1191
+ setters.push(`${be.db.quoteIdent(passAlgorithmFieldName)} = $3`);
1192
+ params.push('PG-SHA256')
1015
1193
  }
1016
- });
1194
+ if(setUpdateDate && passUpdatedAtFieldName){
1195
+ setters.push(`${be.db.quoteIdent(passUpdatedAtFieldName)} = current_timestamp`)
1196
+ }
1197
+
1198
+ const result = await client.query(`
1199
+ UPDATE ${(schema ? be.db.quoteIdent(schema) + '.' : '') + be.db.quoteIdent(table)}
1200
+ SET ${setters.join(', ')}
1201
+ WHERE ${be.db.quoteIdent(userFieldName)} = $1
1202
+ returning 1 as ok
1203
+ `, params
1204
+ ).fetchOneRowIfExists();
1205
+
1206
+ if(result.rowCount === 0 && errorIfNoResult){
1207
+ throw Error ('no se encontró el usuario')
1208
+ };
1209
+ return result
1210
+ }
1017
1211
  mainApp.loginPlusManager.setValidatorStrategy(
1018
- function(req, username, password, done) {
1019
- var client;
1020
- if(!be.config.login["preserve-case"]){
1021
- username = username.toLowerCase().trim();
1022
- }
1023
- be.getDbClient(req).then(function(cli){
1024
- client = cli;
1025
- return client.query("select set_app_user('!login')").execute();
1026
- }).then(function(){
1027
- var infoFieldList=be.config.login.infoFieldList||(be.config.login.rolFieldName?[be.config.login.userFieldName,be.config.login.rolFieldName]:[be.config.login.userFieldName]);
1028
- return client.query(
1029
- "SELECT "+infoFieldList.map(function(fieldOrPair){ return fieldOrPair.split(' as ').map(function(ident){ return be.db.quoteIdent(ident)}).join(' as '); })+
1212
+ async function(req, username, password, done) {
1213
+ try{
1214
+ var client;
1215
+ if(!be.config.login["preserve-case"]){
1216
+ username = username.toLowerCase().trim();
1217
+ }
1218
+ client = await be.getDbClient(req);
1219
+ await client.query("CALL set_app_user('!login')").execute();
1220
+ const {passFieldName, rolFieldName, userFieldName} = be.config.login;
1221
+ var infoFieldList=(
1222
+ be.config.login.infoFieldList||(
1223
+ rolFieldName?
1224
+ [userFieldName, rolFieldName]
1225
+ :
1226
+ [userFieldName]
1227
+ )
1228
+ ).concat(passFieldName);
1229
+
1230
+ const sql = "SELECT "+infoFieldList.map(function(fieldOrPair){ return fieldOrPair.split(' as ').map(function(ident){ return be.db.quoteIdent(ident)}).join(' as '); })+
1030
1231
  ", "+be.config.login.activeClausule+" as active "+
1031
1232
  ", "+be.config.login.lockedClausule+" as locked "+
1032
1233
  " FROM "+(be.config.login.from ?? (
1033
1234
  (be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':'')+
1034
1235
  be.db.quoteIdent(be.config.login.table)
1035
1236
  ))+
1036
- " WHERE "+be.db.quoteIdent(be.config.login.userFieldName)+" = $1 "+
1037
- " AND "+be.db.quoteIdent(be.config.login.passFieldName)+" = $2 ",
1038
- [username, md5(password+username)]
1039
- ).fetchOneRowIfExists();
1040
- }).then(function(data){
1237
+ " WHERE "+be.db.quoteIdent(be.config.login.userFieldName)+" = $1 ";
1238
+ const data = await client.query(sql,[username]).fetchOneRowIfExists();
1239
+
1240
+ if(data.rowCount != 1){
1241
+ done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1242
+ return
1243
+ }else{
1244
+ let needsMigration = false;
1245
+ const user = data.row;
1246
+ if(!user[passFieldName]){
1247
+ done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1248
+ return
1249
+ }
1250
+ const usaScramSha256 = user[passFieldName].startsWith('SCRAM-SHA-256$')
1251
+ if (usaScramSha256){
1252
+ let isScramValid = false;
1253
+ // 1. Intento con formato PG-Compatible (Nuevo)
1254
+ if(await verifyScramPG(password, user[passFieldName])){
1255
+ isScramValid = true;
1256
+ }
1257
+
1258
+ // 2. Intento con formato Legacy (64 bytes)
1259
+ else if(await verifyScramLegacy(password, user[passFieldName])){
1260
+ isScramValid = true;
1261
+ needsMigration = true;
1262
+ }
1263
+ if(!isScramValid){
1264
+ done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1265
+ return
1266
+ }
1267
+ }else{
1268
+ if (md5(password+username.toLowerCase()) === user[passFieldName]){
1269
+ needsMigration = true;
1270
+ }else{
1271
+ done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1272
+ return
1273
+ }
1274
+ }
1275
+ if(needsMigration){
1276
+ if (be.config.log["pass-migration"]) console.log('Ejecutando migración a SCRAM...');
1277
+ await updatePassword({client, username, password, setUpdateDate:false, errorIfNoResult:true});
1278
+ if (be.config.log["pass-migration"]) console.log('Migración completada.');
1279
+ }
1280
+ }
1281
+ //continua validando
1041
1282
  if(data.rowCount==1){
1042
1283
  if(!data.row.active){
1043
1284
  done(null,false,{message:be.messages.unlogged.login.inactiveFail});
1285
+ return
1044
1286
  }else if(data.row.locked){
1045
1287
  done(null,false,{message:be.messages.unlogged.login.lockedFail});
1288
+ return
1046
1289
  }else{
1047
1290
  if(req.query["return-to"]){
1048
1291
  req.session.loginReturnTo = req.query["return-to"];
@@ -1050,12 +1293,13 @@ AppBackend.prototype.start = function start(opts){
1050
1293
  if(be.config.login["double-dragon"]){
1051
1294
  be.DoubleDragon.dbParams[username] = changing(be.config.db, {user:username, password});
1052
1295
  }
1053
- return data.row;
1054
1296
  }
1055
1297
  }else{
1056
1298
  done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1299
+ return
1057
1300
  }
1058
- }).then(async function(userInfo){
1301
+
1302
+ const userInfo = data.row;
1059
1303
  if (!userInfo) return;
1060
1304
  if (!be.config.login.skipBitacora) {
1061
1305
  var context = be.getContext(req);
@@ -1070,56 +1314,73 @@ AppBackend.prototype.start = function start(opts){
1070
1314
  userInfo.bitacoraId = sessionInfo.value;
1071
1315
  }
1072
1316
  done(null, userInfo);
1073
- }).then(function(){
1074
- client.done();
1075
- }).catch(function(err){
1317
+ }catch(err){
1076
1318
  if(be.config.login["double-dragon"]){
1077
1319
  done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1078
1320
  }
1079
- if(client && typeof client.done === "function"){
1080
- client.done();
1081
- }
1082
- throw err;
1083
- }).catch(function(err){
1084
1321
  console.log('login error',err);
1085
1322
  console.log(err.stack);
1086
1323
  done(new Error('internal login error'));
1087
- });
1324
+ } finally {
1325
+ if(client && typeof client.done === "function"){
1326
+ client.done();
1327
+ }
1328
+ }
1088
1329
  }
1089
1330
  );
1090
1331
  be.changePassword=async function(client,username,oldPassword,newPassword){
1091
- var sql = "UPDATE "+(be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':'')+
1092
- be.db.quoteIdent(be.config.login.table)+
1093
- "\n SET "+be.db.quoteIdent(be.config.login.passFieldName)+" = $2 "+
1094
- "\n WHERE "+be.db.quoteIdent(be.config.login.userFieldName)+" = $1 "+
1095
- (oldPassword!==false?"\n AND "+be.db.quoteIdent(be.config.login.passFieldName)+" = $3 ":"")+
1096
- "\n RETURNING 1 as ok";
1097
- var params = [username, md5(newPassword+username.toLowerCase())]
1098
- if(oldPassword!==false){
1099
- params.push(md5(oldPassword+username.toLowerCase()))
1332
+ const { table, passFieldName, userFieldName, schema } = be.config.login;
1333
+ let ok = false;
1334
+ const data = await client.query(
1335
+ `SELECT *
1336
+ FROM ${(schema ? be.db.quoteIdent(schema) + '.' : '')}${be.db.quoteIdent(table)}
1337
+ WHERE ${be.db.quoteIdent(userFieldName)} = $1`,
1338
+ [username]
1339
+ ).fetchOneRowIfExists();
1340
+ if (data.rowCount !== 1) {
1341
+ // Usuario no encontrado
1342
+ return false;
1343
+ }
1344
+ const user = data.row
1345
+ const storedHash = user[passFieldName];
1346
+ if (oldPassword !== false) {
1347
+ if (storedHash.startsWith('SCRAM-SHA-256$')) {//Intento SCRAM-SHA-256
1348
+ ok = await verifyScramPG(oldPassword, storedHash);
1349
+ if (!ok) {
1350
+ ok = await verifyScramLegacy(oldPassword, storedHash);
1351
+ }
1352
+ } else { //Intento MD5
1353
+ const md5Hash = md5(oldPassword + username.toLowerCase());
1354
+ ok = (md5Hash === storedHash);
1355
+ }
1356
+ if (!ok) {
1357
+ // La contraseña antigua es incorrecta
1358
+ return false;
1359
+ }
1360
+ } else {
1361
+ // Si no se requiere la contraseña antigua continua
1362
+ ok = true;
1100
1363
  }
1101
- var result = await client.query(sql, params).fetchOneRowIfExists();
1102
- var ok = result.rowCount == 1;
1103
- if(ok && be.config.login['double-dragon']){
1104
- sql = "ALTER USER " +be.db.quoteIdent(username) + " WITH PASSWORD " + be.db.quoteLiteral(newPassword);
1105
- await client.query(sql,[]).execute();
1364
+ // Si la verificación fue exitosa o se omitió (ok == true), generamos el nuevo hash SCRAM
1365
+ if (ok) {
1366
+ const updateResult = await updatePassword({client, username, password:newPassword, setUpdateDate:true, errorIfNoResult:false});
1367
+ ok = updateResult.rowCount === 1;
1368
+ if (ok && be.config.login['double-dragon']) {
1369
+ const doubleDragonSql = "ALTER USER " + be.db.quoteIdent(username) + " WITH PASSWORD " + be.db.quoteLiteral(newPassword);
1370
+ await client.query(doubleDragonSql, []).execute();
1371
+ }
1106
1372
  }
1107
1373
  return ok;
1108
- }
1374
+ };
1109
1375
  be.passwordChanger=function(req, username, oldPassword, newPassword, done) {
1110
1376
  if(be.config.login.disableChangePassword){
1111
1377
  done(null,false,'el cambio de contraseña está deshabilitado');
1112
1378
  }else{
1113
- var client;
1114
- be.inTransaction(req,function(cli){
1115
- client = cli;
1379
+ be.inTransaction(req,function(client){
1116
1380
  return be.changePassword(client,username,oldPassword,newPassword)
1117
1381
  }).then(function(ok){
1118
1382
  done(null,ok,ok?null:be.messages.server.oldPassDontMatch);
1119
1383
  }).catch(function(err){
1120
- //if(client && client.done){
1121
- // client.done();
1122
- //}
1123
1384
  console.log('error changing pass',err);
1124
1385
  console.log('stack',err.stack);
1125
1386
  throw err;
@@ -1237,6 +1498,10 @@ AppBackend.prototype.specialValueWhenInsert = {
1237
1498
  }
1238
1499
  }
1239
1500
 
1501
+ AppBackend.prototype.specialSqlDefaultExpressions = {
1502
+ 'current_user': 'get_app_user()',
1503
+ }
1504
+
1240
1505
  AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStructure(client){
1241
1506
  var be=this;
1242
1507
  var result = await client.query(`select setting from pg_settings where name='server_version';`).fetchUniqueValue();
@@ -1305,17 +1570,21 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1305
1570
  `;
1306
1571
  throw new Error(message);
1307
1572
  }
1308
- var {rows: sql_routines} = await client.query(`SELECT routine_name, routine_schema, routine_definition
1573
+ var {rows: sql_routines} = await client.query(`SELECT routine_name, routine_schema, routine_type, routine_definition
1309
1574
  FROM information_schema.routines
1310
1575
  WHERE routine_schema in (${be.config.db.search_path.map(path => be.db.quoteLiteral(path)).join(', ')})
1311
1576
  `).fetchAll();
1312
- var sqlRoutines = likeAr.toPlainObject(sql_routines, 'routine_name');
1577
+ var sqlRoutines = likeAr.createIndex(sql_routines, 'routine_name');
1578
+ var set_app_user_RoutineType = sqlRoutines.set_app_user?.routine_type || 'inexsistente';
1579
+ if (set_app_user_RoutineType != 'PROCEDURE') {
1580
+ throw new Error('ERROR en la DB: set_app_user no es un PROCEDURE es ' + set_app_user_RoutineType);
1581
+ }
1313
1582
  var message = ''
1314
1583
  likeAr(AppBackend.prototype.sql_routines).forEach((routine_name, def) => {
1315
1584
  if (sqlRoutines[routine_name] && def.dump.includes(sqlRoutines[routine_name].routine_definition)) {
1316
1585
  message += `
1317
1586
  ----- hay que crear o actualizar la rutina ${routine_name}:
1318
- ${dump}
1587
+ ${def.dump}
1319
1588
  `;
1320
1589
  }
1321
1590
  })
@@ -1887,12 +2156,12 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1887
2156
  })
1888
2157
  }
1889
2158
  })
2159
+ promise.catch(err=>{
2160
+ console.log('error in /new-password');
2161
+ console.log(err);
2162
+ })
1890
2163
  }
1891
2164
  res.redirect(Path.posix.join(baseUrl, be.config.login.forget?.urlPathOk+'?result'+resultCode));
1892
- promise.catch(err=>{
1893
- console.log('error in /new-password');
1894
- console.log(err);
1895
- })
1896
2165
  })
1897
2166
  mainApp.get(Path.posix.join(baseUrl,'/new-pass'), function(req,res,next){
1898
2167
  var resultCode;
@@ -2423,10 +2692,16 @@ AppBackend.prototype.addLoggedServices = function addLoggedServices(){
2423
2692
  throw err;
2424
2693
  }
2425
2694
  });
2426
- be.app.get('/--version',function(req,res,next){
2695
+ be.app.get('/--version',async function(req,res,next){
2696
+ var simpleGit = require('simple-git');
2697
+ var git = simpleGit(process.cwd());
2427
2698
  var info=[
2428
2699
  html.h1(be.messages.server.versions),
2429
2700
  html.h3([packagejson.name,' ',packagejson.version]),
2701
+ html.ul([
2702
+ html.li([(await git.revparse(['HEAD'])).trim()]),
2703
+ ...((git.tags().all || []).map(tag => html.li([tag]))),
2704
+ ])
2430
2705
  ];
2431
2706
  Promise.resolve().then(function(){
2432
2707
  if(be.isAdmin(req)){
@@ -2675,8 +2950,12 @@ AppBackend.prototype.dumpDbTableFields = function dumpDbTableFields(tableDef, op
2675
2950
  fields.push(
2676
2951
  ' '+db.quoteIdent(fieldDef.name)+
2677
2952
  ' '+(fieldDef.dataLength?(fieldType=='text'?'varchar':fieldType)+'('+fieldDef.dataLength+')':fieldType)+
2678
- (fieldDef.defaultValue!=null?' default '+db.quoteLiteral(fieldDef.defaultValue):'')+
2679
- (fieldDef.defaultDbValue!=null?' default '+fieldDef.defaultDbValue:'')+
2953
+ ( be.specialSqlDefaultExpressions[fieldDef.defaultDbValue] != null ? ' default ' + be.specialSqlDefaultExpressions[fieldDef.defaultDbValue]
2954
+ : fieldDef.defaultDbValue != null ? ' default ' + fieldDef.defaultDbValue
2955
+ : be.specialSqlDefaultExpressions[fieldDef.specialDefaultValue] != null ? ' default ' + be.specialSqlDefaultExpressions[fieldDef.specialDefaultValue]
2956
+ : fieldDef.defaultValue != null ? ' default ' + db.quoteLiteral(fieldDef.defaultValue)
2957
+ : ''
2958
+ ) +
2680
2959
  (be.isGeneratedSequence(fieldDef.sequence)?' generated always as identity':'')+
2681
2960
  (fieldDef.generatedAs!=null?` generated always as (${fieldDef.generatedAs}) stored`:'')
2682
2961
  );
@@ -2966,8 +3245,8 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2966
3245
  }
2967
3246
  }
2968
3247
  lines.push(`
2969
- create or replace function set_app_user(p_user text) returns text
2970
- security definer volatile language plpgsql
3248
+ create or replace procedure set_app_user(p_username text)
3249
+ security definer language plpgsql
2971
3250
  as
2972
3251
  $body$
2973
3252
  declare
@@ -2975,7 +3254,7 @@ declare
2975
3254
  ${db.quoteIdent('v_'+fieldName)} text;`
2976
3255
  ).join('')}
2977
3256
  begin
2978
- if p_user = '!login' then
3257
+ if p_username = '!login' then
2979
3258
  ${be.config.login.infoFieldList.map(fieldName => `
2980
3259
  set backend_plus._${fieldName} = '!';`).join('')}
2981
3260
 
@@ -2987,14 +3266,13 @@ begin
2987
3266
  from ${(be.config.login.from ?? (
2988
3267
  (be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':'')+
2989
3268
  be.db.quoteIdent(be.config.login.table)))}
2990
- where ${db.quoteIdent(be.config.login.userFieldName)} = p_user;
3269
+ where ${db.quoteIdent(be.config.login.userFieldName)} = p_username;
2991
3270
  ${be.config.login.infoFieldList.map(fieldName => `
2992
3271
  perform set_config('backend_plus._${fieldName}', v_${fieldName}, false);`).join('')}
2993
3272
 
2994
3273
  set backend_plus._mode = normal;
2995
3274
  end if;
2996
- perform set_config('backend_plus._user', p_user, false);
2997
- return p_user;
3275
+ perform set_config('backend_plus._user', p_username, false);
2998
3276
  end;
2999
3277
  $body$;
3000
3278
 
@@ -3358,8 +3636,10 @@ AppBackend.prototype.exportacionesGenerico = async function exportacionesGeneric
3358
3636
  }
3359
3637
  }
3360
3638
  }
3361
- await buscarGenerados(csvFileName);
3362
- await buscarGenerados(fileName);
3639
+ if (!procedureDef.forExport.generarInmediato) {
3640
+ await buscarGenerados(csvFileName);
3641
+ await buscarGenerados(fileName);
3642
+ }
3363
3643
  if(hayGenerados.length>0){
3364
3644
  return hayGenerados;
3365
3645
  }
@@ -137,6 +137,9 @@ function tableDefAdapt(tableDef, context){
137
137
  resultTableDef.field[fieldDef.name]=fieldDef;
138
138
  if(fieldDef.isName){
139
139
  resultTableDef.nameFields.push(fieldDef.name);
140
+ if (fieldDef.isName == 'known' && resultTableDef.hiddenColumns && !resultTableDef.hiddenColumns.includes(fieldDef.name)) {
141
+ resultTableDef.hiddenColumns.push(fieldDef.name);
142
+ }
140
143
  }
141
144
  return fieldDef;
142
145
  });
@@ -174,7 +177,7 @@ function tableDefAdapt(tableDef, context){
174
177
  if(resultTableDef.sortColumns){
175
178
  resultTableDef.sortColumns.map(function(sortColumn){
176
179
  sortColumn.order = sortColumn.order || 1;
177
- if (!resultTableDef.field[sortColumn.column]) {
180
+ if (!resultTableDef.field[sortColumn.column] && !/__/.test(sortColumn.column)) {
178
181
  throw new Error("unkown column " + JSON.stringify(sortColumn.column) + " in sortColumn (or Primarykey) of " + resultTableDef.name);
179
182
  }
180
183
  });
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.5.2-betha.9",
4
+ "version": "2.5.2",
5
5
  "author": "Codenautas <codenautas@googlegroups.com>",
6
6
  "license": "MIT",
7
7
  "repository": "codenautas/backend-plus",
@@ -30,64 +30,65 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@upgraded/locate-path": "^6.0.0-alfa.1",
33
- "ajax-best-promise": "^0.4.2",
34
- "backend-skins": "^0.1.15",
35
- "best-globals": "^2.0.1",
33
+ "ajax-best-promise": "^0.4.3",
34
+ "backend-skins": "^0.1.22",
35
+ "best-globals": "^2.0.7",
36
36
  "big.js": "^7.0.1",
37
- "body-parser": "^2.2.0",
38
- "cast-error": "^0.1.2",
39
- "castellano": "^0.1.4",
37
+ "body-parser": "^2.2.2",
38
+ "cast-error": "^0.1.3",
39
+ "castellano": "^0.1.5",
40
40
  "connect-pg-simple": "^10.0.0",
41
41
  "cookie-parser": "^1.4.7",
42
- "cors": "^2.8.5",
43
- "dialog-promise": "^0.10.2",
42
+ "cors": "^2.8.6",
43
+ "dialog-promise": "^0.10.5",
44
44
  "discrepances": "^0.2.9",
45
- "express": "^5.1.0",
46
- "express-session": "^1.18.1",
47
- "express-useragent": "^1.0.15",
48
- "fs-extra": "^11.3.0",
49
- "js-to-html": "^1.3.2",
50
- "js-yaml": "^4.1.0",
51
- "json4all": "^1.4.0",
45
+ "express": "^5.2.1",
46
+ "express-session": "^1.19.0",
47
+ "express-useragent": "^2.1.0",
48
+ "fs-extra": "^11.3.3",
49
+ "js-to-html": "^1.3.5",
50
+ "js-yaml": "^4.1.1",
51
+ "json4all": "^1.4.2",
52
52
  "lazy-some": "^0.1.0",
53
- "like-ar": "^0.5.1",
54
- "login-plus": "^1.7.3",
53
+ "like-ar": "^0.5.2",
54
+ "login-plus": "^1.8.1",
55
55
  "memorystore": "^1.6.7",
56
- "mini-tools": "^1.13.3",
56
+ "mini-tools": "^1.13.5",
57
57
  "moment": "^2.30.1",
58
58
  "multiparty": "^4.2.3",
59
- "nodemailer": "^7.0.3",
59
+ "nodemailer": "^7.0.13",
60
60
  "numeral": "^2.0.6",
61
- "pg-promise-strict": "^1.4.3",
62
- "pg-triggers": "0.4.3",
61
+ "pg-promise-strict": "^1.4.5",
62
+ "pg-triggers": "0.4.6",
63
63
  "pikaday": "^1.8.2",
64
64
  "pug": "^3.0.3",
65
65
  "read-yaml-promise": "^1.0.2",
66
66
  "regexplicit": "^0.1.3",
67
- "require-bro": "^0.3.1",
67
+ "require-bro": "^0.3.4",
68
68
  "self-explain": "^0.11.0",
69
- "serve-content": "^0.4.0",
69
+ "serve-content": "^1.0.4",
70
70
  "session-file-store": "^1.5.0",
71
- "sql-tools": "^0.1.2",
71
+ "simple-git": "^3.30.0",
72
+ "sql-tools": "^0.1.7",
72
73
  "stack-trace": "^0.0.10",
73
74
  "stylus": "0.64.0",
74
- "type-store": "^0.4.5",
75
- "typed-controls": "^0.12.2",
75
+ "type-store": "^0.4.6",
76
+ "typed-controls": "^0.12.4",
76
77
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
77
78
  },
78
79
  "devDependencies": {
79
80
  "@types/big.js": "^6.2.2",
80
81
  "@types/expect.js": "~0.3.32",
81
- "@types/express": "^5.0.3",
82
+ "@types/express": "^5.0.6",
82
83
  "@types/express-useragent": "^1.0.5",
83
84
  "@types/fs-extra": "^11.0.4",
84
85
  "@types/js-yaml": "^4.0.9",
85
86
  "@types/mocha": "^10.0.10",
86
87
  "@types/multiparty": "~4.2.1",
87
- "@types/node": "^24.0.3",
88
- "@types/nodemailer": "^6.4.17",
88
+ "@types/node": "^25.2.0",
89
+ "@types/nodemailer": "^7.0.9",
89
90
  "@types/numeral": "~2.0.5",
90
- "@types/session-file-store": "^1.2.5",
91
+ "@types/session-file-store": "^1.2.6",
91
92
  "@types/stack-trace": "~0.0.33",
92
93
  "@types/websql": "~0.0.30",
93
94
  "esprima": "^4.0.1",
@@ -99,20 +100,21 @@
99
100
  "karma-ie-launcher": "^1.0.0",
100
101
  "karma-mocha": "^2.0.1",
101
102
  "kill-9": "~0.4.3",
102
- "mocha": "^11.7.0",
103
+ "mocha": "^11.7.5",
103
104
  "nyc": "^17.1.0",
104
- "puppeteer": "^24.10.2",
105
- "sinon": "^21.0.0",
106
- "supertest": "^7.1.1",
105
+ "puppeteer": "^24.36.1",
106
+ "sinon": "^21.0.1",
107
+ "supertest": "^7.2.2",
107
108
  "types.d.ts": "~0.6.22",
108
- "typescript": "^5.8.3",
109
+ "typescript": "^5.9.3",
109
110
  "why-is-node-running": "^3.2.2"
110
111
  },
111
112
  "engines": {
112
113
  "node": ">= 18"
113
114
  },
114
115
  "scripts": {
115
- "test": "(npm run prepublish || echo \"continue w/error\") && mocha --reporter spec --single-run --bail test/test-*.js",
116
+ "test": "echo testear SiPer que usa la mayoría de la funcionalidad de Backend-Plus",
117
+ "test-ui": "(npm run prepublish || echo \"continue w/error\") && mocha --reporter spec --single-run --bail test/test-*.js",
116
118
  "test-karma": "(npm run prepublish || echo \"continue w/error\") && mocha --reporter spec --bail test/test-k*.js",
117
119
  "test-why": "node --expose-internals ./node_modules/mocha/bin/_mocha --reporter spec --bail test/test*.js",
118
120
  "test-ci": "(npm run prepublish || echo \"continue w/error\") && mocha --reporter spec --bail test/test*.js",