backend-plus 2.5.2-betha.23 → 2.5.2-betha.25
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/lib/backend-plus.d.ts +4 -1
- package/lib/backend-plus.js +215 -42
- package/package.json +1 -1
package/lib/backend-plus.d.ts
CHANGED
|
@@ -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
|
|
|
@@ -247,7 +248,9 @@ export interface ForeignKey {
|
|
|
247
248
|
onUpdate?: FkActions,
|
|
248
249
|
onDelete?: FkActions,
|
|
249
250
|
displayAllFields?: boolean,
|
|
250
|
-
alias?:string,
|
|
251
|
+
alias?:string,
|
|
252
|
+
abr?:string,
|
|
253
|
+
label?:string,
|
|
251
254
|
displayFields?:string[],
|
|
252
255
|
consName?:string,
|
|
253
256
|
initiallyDeferred?:boolean
|
package/lib/backend-plus.js
CHANGED
|
@@ -83,7 +83,104 @@ 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
|
+
const KEY_LENGTH = 64; // Longitud de la clave (64 bytes)
|
|
89
|
+
const bufferToBase64 = (buffer) => buffer.toString('base64');
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Función central para PBKDF2.
|
|
93
|
+
* Devuelve la clave derivada.
|
|
94
|
+
*/
|
|
95
|
+
function deriveKey(password, salt, iterations) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
crypto.pbkdf2(
|
|
98
|
+
password,
|
|
99
|
+
salt,
|
|
100
|
+
iterations,
|
|
101
|
+
KEY_LENGTH,
|
|
102
|
+
HASH_ALGORITHM,
|
|
103
|
+
(err, derivedKey) => {
|
|
104
|
+
if (err) return reject(err);
|
|
105
|
+
resolve(derivedKey);
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Genera la cadena de verificación SCRAM-SHA-256 en formato PostgreSQL.
|
|
113
|
+
* Formato: SCRAM-SHA-256$iterations:salt$verifier
|
|
114
|
+
* @param {string} password - Contraseña en texto plano.
|
|
115
|
+
* @returns {Promise<string>} La cadena SCRAM completa.
|
|
116
|
+
*/
|
|
117
|
+
async function generateScramVerifier(password) {
|
|
118
|
+
// Genera Salt aleatoria
|
|
119
|
+
const saltBuffer = crypto.randomBytes(32);
|
|
120
|
+
const saltBase64 = bufferToBase64(saltBuffer);
|
|
121
|
+
|
|
122
|
+
// Deriva la clave (Verifier)
|
|
123
|
+
const verifierBuffer = await deriveKey(
|
|
124
|
+
password,
|
|
125
|
+
saltBase64,
|
|
126
|
+
DEFAULT_ITERATIONS
|
|
127
|
+
);
|
|
128
|
+
const verifierBase64 = bufferToBase64(verifierBuffer);
|
|
129
|
+
|
|
130
|
+
// Ensambla la cadena final (PostgreSQL style)
|
|
131
|
+
// Usa $ como delimitador principal (aunque PostgreSQL lo pone dentro de la tabla).
|
|
132
|
+
// El formato canónico es 'SCRAM-SHA-256$i:salt, verifier$encoded_verifier'
|
|
133
|
+
return `SCRAM-SHA-256$${DEFAULT_ITERATIONS}:${saltBase64}$${verifierBase64}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Verifica una contraseña en texto plano contra la cadena SCRAM almacenada.
|
|
138
|
+
* @param {string} password - Contraseña en texto plano ingresada por el usuario.
|
|
139
|
+
* @param {string} storedScramString - Cadena SCRAM completa (ej: SCRAM-SHA-256$4096:SALT_BASE64$VERIFIER_BASE64).
|
|
140
|
+
* @returns {Promise<boolean>} True si la contraseña es válida.
|
|
141
|
+
*/
|
|
142
|
+
async function verifyScram(password, storedScramString) {
|
|
143
|
+
if (!storedScramString.startsWith('SCRAM-SHA-256$')) {
|
|
144
|
+
// No es formato SCRAM. Debe ser MD5 u otro algoritmo.
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Parsea la cadena SCRAM
|
|
149
|
+
// Ejemplo: SCRAM-SHA-256$4096:SALT_BASE64$VERIFIER_BASE64
|
|
150
|
+
const parts = storedScramString.split('$');
|
|
151
|
+
if (parts.length !== 3) {
|
|
152
|
+
throw new Error('Formato SCRAM almacenado inválido.');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Obtiene iteraciones y salt de la segunda parte (ej: '4096:SALT_BASE64')
|
|
156
|
+
const [storedIterations, storedSalt] = parts[1].split(':');
|
|
157
|
+
const storedVerifier = parts[2];
|
|
158
|
+
|
|
159
|
+
if (!storedSalt || !storedVerifier || isNaN(parseInt(storedIterations))) {
|
|
160
|
+
throw new Error('Datos de SCRAM incompletos o malformados.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const iterations = parseInt(storedIterations);
|
|
164
|
+
|
|
165
|
+
// Deriva la clave de la contraseña ingresada
|
|
166
|
+
const generatedVerifierBuffer = await deriveKey(
|
|
167
|
+
password,
|
|
168
|
+
storedSalt,
|
|
169
|
+
iterations
|
|
170
|
+
);
|
|
171
|
+
const generatedVerifier = bufferToBase64(generatedVerifierBuffer);
|
|
172
|
+
|
|
173
|
+
// Compara de manera segura contra el Verificador Almacenado
|
|
174
|
+
const storedVerifierBuffer = Buffer.from(storedVerifier, 'base64');
|
|
175
|
+
const generatedVerifierBufferFromBase64 = Buffer.from(generatedVerifier, 'base64');
|
|
176
|
+
|
|
177
|
+
// Usa crypto.timingSafeEqual para evitar ataques de temporización
|
|
178
|
+
if (generatedVerifierBufferFromBase64.length !== storedVerifierBuffer.length) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return crypto.timingSafeEqual(generatedVerifierBufferFromBase64, storedVerifierBuffer);
|
|
183
|
+
}
|
|
87
184
|
|
|
88
185
|
var dist=regexpDistCheck.test(packagejson.main)?'dist/':'';
|
|
89
186
|
|
|
@@ -1023,34 +1120,77 @@ AppBackend.prototype.start = function start(opts){
|
|
|
1023
1120
|
}
|
|
1024
1121
|
});
|
|
1025
1122
|
mainApp.loginPlusManager.setValidatorStrategy(
|
|
1026
|
-
function(req, username, password, done) {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
client =
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
var infoFieldList=
|
|
1036
|
-
|
|
1037
|
-
|
|
1123
|
+
async function(req, username, password, done) {
|
|
1124
|
+
try{
|
|
1125
|
+
var client;
|
|
1126
|
+
if(!be.config.login["preserve-case"]){
|
|
1127
|
+
username = username.toLowerCase().trim();
|
|
1128
|
+
}
|
|
1129
|
+
client = await be.getDbClient(req);
|
|
1130
|
+
client.query("CALL set_app_user('!login')").execute();
|
|
1131
|
+
const {passFieldName, rolFieldName, userFieldName} = be.config.login;
|
|
1132
|
+
var infoFieldList=(
|
|
1133
|
+
be.config.login.infoFieldList||(
|
|
1134
|
+
rolFieldName?
|
|
1135
|
+
[userFieldName, rolFieldName]
|
|
1136
|
+
:
|
|
1137
|
+
[userFieldName]
|
|
1138
|
+
)
|
|
1139
|
+
).concat(passFieldName);
|
|
1140
|
+
|
|
1141
|
+
const sql = "SELECT "+infoFieldList.map(function(fieldOrPair){ return fieldOrPair.split(' as ').map(function(ident){ return be.db.quoteIdent(ident)}).join(' as '); })+
|
|
1038
1142
|
", "+be.config.login.activeClausule+" as active "+
|
|
1039
1143
|
", "+be.config.login.lockedClausule+" as locked "+
|
|
1040
1144
|
" FROM "+(be.config.login.from ?? (
|
|
1041
1145
|
(be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':'')+
|
|
1042
1146
|
be.db.quoteIdent(be.config.login.table)
|
|
1043
1147
|
))+
|
|
1044
|
-
" WHERE "+be.db.quoteIdent(be.config.login.userFieldName)+" = $1 "
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1148
|
+
" WHERE "+be.db.quoteIdent(be.config.login.userFieldName)+" = $1 ";
|
|
1149
|
+
const data = await client.query(sql,[username]).fetchOneRowIfExists();
|
|
1150
|
+
|
|
1151
|
+
if(data.rowCount != 1){
|
|
1152
|
+
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1153
|
+
return
|
|
1154
|
+
}else{
|
|
1155
|
+
const user = data.row;
|
|
1156
|
+
const usaScramSha256 = user[passFieldName].startsWith('SCRAM-SHA-256$')
|
|
1157
|
+
if (usaScramSha256){
|
|
1158
|
+
const isScramValid = await verifyScram(
|
|
1159
|
+
password,
|
|
1160
|
+
user[passFieldName],
|
|
1161
|
+
)
|
|
1162
|
+
if(!isScramValid){
|
|
1163
|
+
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1164
|
+
return
|
|
1165
|
+
}
|
|
1166
|
+
}else{
|
|
1167
|
+
if (md5(password+username.toLowerCase()) === user[passFieldName]) {
|
|
1168
|
+
console.log('Autenticación MD5 OK. Ejecutando migración a SCRAM...');
|
|
1169
|
+
// Generar nuevos valores SCRAM
|
|
1170
|
+
const hashPass = await generateScramVerifier(password);
|
|
1171
|
+
//sobrescribo el hash MD5 antiguo
|
|
1172
|
+
await client.query(`
|
|
1173
|
+
UPDATE usuarios
|
|
1174
|
+
SET ${be.db.quoteIdent(passFieldName)} = $1
|
|
1175
|
+
WHERE usuario = $2
|
|
1176
|
+
returning *`,
|
|
1177
|
+
[hashPass, username]
|
|
1178
|
+
).fetchUniqueRow();
|
|
1179
|
+
console.log('Migración completada.');
|
|
1180
|
+
}else{
|
|
1181
|
+
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1182
|
+
return
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
//continua validando
|
|
1049
1187
|
if(data.rowCount==1){
|
|
1050
1188
|
if(!data.row.active){
|
|
1051
1189
|
done(null,false,{message:be.messages.unlogged.login.inactiveFail});
|
|
1190
|
+
return
|
|
1052
1191
|
}else if(data.row.locked){
|
|
1053
1192
|
done(null,false,{message:be.messages.unlogged.login.lockedFail});
|
|
1193
|
+
return
|
|
1054
1194
|
}else{
|
|
1055
1195
|
if(req.query["return-to"]){
|
|
1056
1196
|
req.session.loginReturnTo = req.query["return-to"];
|
|
@@ -1058,12 +1198,13 @@ AppBackend.prototype.start = function start(opts){
|
|
|
1058
1198
|
if(be.config.login["double-dragon"]){
|
|
1059
1199
|
be.DoubleDragon.dbParams[username] = changing(be.config.db, {user:username, password});
|
|
1060
1200
|
}
|
|
1061
|
-
return data.row;
|
|
1062
1201
|
}
|
|
1063
1202
|
}else{
|
|
1064
1203
|
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1204
|
+
return
|
|
1065
1205
|
}
|
|
1066
|
-
|
|
1206
|
+
|
|
1207
|
+
const userInfo = data.row;
|
|
1067
1208
|
if (!userInfo) return;
|
|
1068
1209
|
if (!be.config.login.skipBitacora) {
|
|
1069
1210
|
var context = be.getContext(req);
|
|
@@ -1078,42 +1219,72 @@ AppBackend.prototype.start = function start(opts){
|
|
|
1078
1219
|
userInfo.bitacoraId = sessionInfo.value;
|
|
1079
1220
|
}
|
|
1080
1221
|
done(null, userInfo);
|
|
1081
|
-
}).then(function(){
|
|
1082
1222
|
client.done();
|
|
1083
|
-
}
|
|
1223
|
+
}catch(err){
|
|
1084
1224
|
if(be.config.login["double-dragon"]){
|
|
1085
1225
|
done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
|
|
1086
1226
|
}
|
|
1087
1227
|
if(client && typeof client.done === "function"){
|
|
1088
1228
|
client.done();
|
|
1089
1229
|
}
|
|
1090
|
-
throw err;
|
|
1091
|
-
}).catch(function(err){
|
|
1092
1230
|
console.log('login error',err);
|
|
1093
1231
|
console.log(err.stack);
|
|
1094
1232
|
done(new Error('internal login error'));
|
|
1095
|
-
}
|
|
1233
|
+
};
|
|
1096
1234
|
}
|
|
1097
1235
|
);
|
|
1098
1236
|
be.changePassword=async function(client,username,oldPassword,newPassword){
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1237
|
+
const { table, passFieldName, userFieldName } = be.config.login;
|
|
1238
|
+
const { schema } = be.config.db;
|
|
1239
|
+
let ok = false;
|
|
1240
|
+
const data = await client.query(
|
|
1241
|
+
`SELECT *
|
|
1242
|
+
FROM ${(schema ? be.db.quoteIdent(schema) + '.' : '')}${be.db.quoteIdent(table)}
|
|
1243
|
+
WHERE ${be.db.quoteIdent(userFieldName)} = $1`,
|
|
1244
|
+
[username]
|
|
1245
|
+
).fetchOneRowIfExists();
|
|
1246
|
+
if (data.rowCount !== 1) {
|
|
1247
|
+
// Usuario no encontrado
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
const user = data.row
|
|
1251
|
+
const storedHash = user[passFieldName];
|
|
1252
|
+
if (oldPassword !== false) {
|
|
1253
|
+
if (storedHash.startsWith('SCRAM-SHA-256$')) {//Intento SCRAM-SHA-256
|
|
1254
|
+
ok = await verifyScram(oldPassword, storedHash);
|
|
1255
|
+
} else { //Intento MD5
|
|
1256
|
+
const md5Hash = md5(oldPassword + username.toLowerCase());
|
|
1257
|
+
ok = (md5Hash === storedHash);
|
|
1258
|
+
}
|
|
1259
|
+
if (!ok) {
|
|
1260
|
+
// La contraseña antigua es incorrecta
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
} else {
|
|
1264
|
+
// Si no se requiere la contraseña antigua continua
|
|
1265
|
+
ok = true;
|
|
1108
1266
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1267
|
+
// Si la verificación fue exitosa o se omitió (ok == true), generamos el nuevo hash SCRAM
|
|
1268
|
+
if (ok) {
|
|
1269
|
+
// hash en ormato completo de PostgreSQL: SCRAM-SHA-256$i:salt$verifier
|
|
1270
|
+
const newScramVerifier = await generateScramVerifier(newPassword);
|
|
1271
|
+
const updateSql =
|
|
1272
|
+
"UPDATE " + (schema ? be.db.quoteIdent(schema) + '.' : '') +
|
|
1273
|
+
be.db.quoteIdent(table) +
|
|
1274
|
+
"\n SET " + be.db.quoteIdent(passFieldName) + " = $2 " +
|
|
1275
|
+
"\n WHERE " + be.db.quoteIdent(userFieldName) + " = $1 " +
|
|
1276
|
+
"\n RETURNING 1 as ok";
|
|
1277
|
+
|
|
1278
|
+
const updateParams = [username, newScramVerifier];
|
|
1279
|
+
const updateResult = await client.query(updateSql, updateParams).fetchOneRowIfExists();
|
|
1280
|
+
ok = updateResult.rowCount === 1;
|
|
1281
|
+
if (ok && be.config.login['double-dragon']) {
|
|
1282
|
+
const doubleDragonSql = "ALTER USER " + be.db.quoteIdent(username) + " WITH PASSWORD " + be.db.quoteLiteral(newPassword);
|
|
1283
|
+
await client.query(doubleDragonSql, []).execute();
|
|
1284
|
+
}
|
|
1114
1285
|
}
|
|
1115
1286
|
return ok;
|
|
1116
|
-
}
|
|
1287
|
+
};
|
|
1117
1288
|
be.passwordChanger=function(req, username, oldPassword, newPassword, done) {
|
|
1118
1289
|
if(be.config.login.disableChangePassword){
|
|
1119
1290
|
done(null,false,'el cambio de contraseña está deshabilitado');
|
|
@@ -3384,8 +3555,10 @@ AppBackend.prototype.exportacionesGenerico = async function exportacionesGeneric
|
|
|
3384
3555
|
}
|
|
3385
3556
|
}
|
|
3386
3557
|
}
|
|
3387
|
-
|
|
3388
|
-
|
|
3558
|
+
if (!procedureDef.forExport.generarInmediato) {
|
|
3559
|
+
await buscarGenerados(csvFileName);
|
|
3560
|
+
await buscarGenerados(fileName);
|
|
3561
|
+
}
|
|
3389
3562
|
if(hayGenerados.length>0){
|
|
3390
3563
|
return hayGenerados;
|
|
3391
3564
|
}
|
package/package.json
CHANGED