This commit is contained in:
naielv
2025-12-25 19:15:28 +01:00
parent 0bc662dbde
commit 90b8223385
10 changed files with 84 additions and 20 deletions

View File

@@ -554,15 +554,14 @@ const SC_actions = {
}, },
], ],
}; };
// Listado precargado de personas:
function TS_decrypt(input, secret, callback, table, id) { function TS_decrypt(input, secret, callback, table, id) {
// Accept objects or plaintext strings. Also support legacy RSA{...} AES-encrypted entries. // Accept objects or plaintext strings. Support AES-encrypted entries wrapped as RSA{...}.
if (typeof input !== "string") { if (typeof input !== "string") {
try { callback(input, false); } catch (e) { console.error(e); } try { callback(input, false); } catch (e) { console.error(e); }
return; return;
} }
// Legacy encrypted format: RSA{...} // Encrypted format marker: RSA{<ciphertext>} where <ciphertext> is CryptoJS AES output
if (input.startsWith("RSA{") && input.endsWith("}") && typeof CryptoJS !== 'undefined') { if (input.startsWith("RSA{") && input.endsWith("}") && typeof CryptoJS !== 'undefined') {
try { try {
var data = input.slice(4, -1); var data = input.slice(4, -1);
@@ -571,7 +570,6 @@ function TS_decrypt(input, secret, callback, table, id) {
try { try {
decryptedUtf8 = words.toString(CryptoJS.enc.Utf8); decryptedUtf8 = words.toString(CryptoJS.enc.Utf8);
} catch (utfErr) { } catch (utfErr) {
// Malformed UTF-8 — try Latin1 fallback
try { try {
decryptedUtf8 = words.toString(CryptoJS.enc.Latin1); decryptedUtf8 = words.toString(CryptoJS.enc.Latin1);
} catch (latinErr) { } catch (latinErr) {
@@ -583,9 +581,9 @@ function TS_decrypt(input, secret, callback, table, id) {
var parsed = null; var parsed = null;
try { parsed = JSON.parse(decryptedUtf8); } catch (pe) { parsed = decryptedUtf8; } try { parsed = JSON.parse(decryptedUtf8); } catch (pe) { parsed = decryptedUtf8; }
try { callback(parsed, true); } catch (e) { console.error(e); } try { callback(parsed, true); } catch (e) { console.error(e); }
// Migrate to plaintext in DB if table/id provided // Keep encrypted at-rest: if table/id provided, ensure DB stores encrypted payload (input)
if (table && id && window.DB && DB.put && typeof parsed !== 'string') { if (table && id && window.DB && DB.put) {
DB.put(table, id, parsed).catch(() => {}); DB.put(table, id, input).catch(() => {});
} }
return; return;
} catch (e) { } catch (e) {
@@ -595,12 +593,14 @@ function TS_decrypt(input, secret, callback, table, id) {
} }
} }
// Try to parse JSON strings and migrate to object // Plain JSON stored as text -> parse and return, then re-encrypt in DB for at-rest protection
try { try {
var parsed = JSON.parse(input); var parsed = JSON.parse(input);
try { callback(parsed, false); } catch (e) { console.error(e); } try { callback(parsed, false); } catch (e) { console.error(e); }
if (table && id && window.DB && DB.put) { if (table && id && window.DB && DB.put && typeof SECRET !== 'undefined') {
DB.put(table, id, parsed).catch(() => {}); TS_encrypt(parsed, SECRET, function (enc) {
DB.put(table, id, enc).catch(() => {});
});
} }
} catch (e) { } catch (e) {
// Not JSON, return raw string // Not JSON, return raw string
@@ -608,10 +608,27 @@ function TS_decrypt(input, secret, callback, table, id) {
} }
} }
function TS_encrypt(input, secret, callback, mode = "RSA") { function TS_encrypt(input, secret, callback, mode = "RSA") {
// Encryption removed: store plaintext objects directly // Encrypt given value for at-rest storage using CryptoJS AES.
try { callback(input); } catch (e) { console.error(e); } // Always return string of form RSA{<ciphertext>} via callback.
try {
if (typeof CryptoJS === 'undefined') {
// CryptoJS not available — return plaintext
try { callback(input); } catch (e) { console.error(e); }
return;
}
var payload = input;
if (typeof input !== 'string') {
try { payload = JSON.stringify(input); } catch (e) { payload = String(input); }
}
var encrypted = CryptoJS.AES.encrypt(payload, secret).toString();
var out = 'RSA{' + encrypted + '}';
try { callback(out); } catch (e) { console.error(e); }
} catch (e) {
console.error('TS_encrypt: encryption failed', e);
try { callback(input); } catch (err) { console.error(err); }
}
} }
// Populate SC_Personas from DB (PouchDB) // Listado precargado de personas:
DB.map('personas', (data, key) => { DB.map('personas', (data, key) => {
function add_row(data, key) { function add_row(data, key) {
if (data != null) { if (data != null) {
@@ -771,7 +788,7 @@ function TS_IndexElement(
var tablebody_EL = document.getElementById(tablebody); var tablebody_EL = document.getElementById(tablebody);
var rows = {}; var rows = {};
config.forEach((key) => { config.forEach((key) => {
tablehead_EL.innerHTML += `<th>${key.label}</th>`; tablehead_EL.innerHTML += `<th>${key.label || ""}</th>`;
}); });
// Add search functionality // Add search functionality
const searchKeyEl = document.getElementById(searchKeyInput); const searchKeyEl = document.getElementById(searchKeyInput);
@@ -874,6 +891,14 @@ function TS_IndexElement(
} }
config.forEach((key) => { config.forEach((key) => {
switch (key.type) { switch (key.type) {
case "_encrypted": {
const tdEncrypted = document.createElement("td");
tdEncrypted.innerText = data["_encrypted__"]
? "🔒"
: "";
new_tr.appendChild(tdEncrypted);
break;
}
case "raw": case "raw":
case "text": { case "text": {
const tdRaw = document.createElement("td"); const tdRaw = document.createElement("td");
@@ -1170,9 +1195,11 @@ function TS_IndexElement(
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data, wasEncrypted) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
data["_encrypted__"] = wasEncrypted;
add_row(data, key); add_row(data, key);
}, ref, key); }, ref, key);
} else { } else {
data["_encrypted__"] = false;
add_row(data, key); add_row(data, key);
} }
}); });
@@ -1191,9 +1218,11 @@ function TS_IndexElement(
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data, wasEncrypted) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
data["_encrypted__"] = wasEncrypted;
add_row(data, key); add_row(data, key);
}, undefined, undefined); }, undefined, undefined);
} else { } else {
data["_encrypted__"] = false;
add_row(data, key); add_row(data, key);
} }
}); });

View File

@@ -5,7 +5,6 @@
var DB = (function () { var DB = (function () {
let local = null; let local = null;
let secret = null;
let remote = null; let remote = null;
let changes = null; let changes = null;
let repPush = null; let repPush = null;
@@ -31,9 +30,15 @@ var DB = (function () {
} }
function init(opts) { function init(opts) {
// opts: { secret, remoteServer, username, password, dbname } // opts: { remoteServer, username, password, dbname }
secret = opts.secret || '';
const localName = 'telesec'; const localName = 'telesec';
// Allow passing encryption secret via opts
try {
if (opts && opts.secret) {
SECRET = opts.secret;
try { localStorage.setItem('TELESEC_SECRET', SECRET); } catch (e) {}
}
} catch (e) {}
local = new PouchDB(localName); local = new PouchDB(localName);
if (opts.remoteServer) { if (opts.remoteServer) {
@@ -109,7 +114,21 @@ var DB = (function () {
return; return;
} }
const doc = existing || { _id: _id }; const doc = existing || { _id: _id };
doc.data = data; // If TS_encrypt is available and a SECRET is configured, encrypt non-encrypted payloads
var toStore = data;
try {
var isEncryptedString = (typeof data === 'string' && data.startsWith('RSA{') && data.endsWith('}'));
if (!isEncryptedString && typeof TS_encrypt === 'function' && typeof SECRET !== 'undefined' && SECRET) {
toStore = await new Promise((resolve) => {
try {
TS_encrypt(data, SECRET, function (enc) {
resolve(enc);
});
} catch (e) { resolve(data); }
});
}
} catch (e) { toStore = data; }
doc.data = toStore;
doc.table = table; doc.table = table;
doc.ts = new Date().toISOString(); doc.ts = new Date().toISOString();
if (existing) doc._rev = existing._rev; if (existing) doc._rev = existing._rev;
@@ -234,9 +253,10 @@ window.DB = DB;
const username = localStorage.getItem('TELESEC_COUCH_USER') || ''; const username = localStorage.getItem('TELESEC_COUCH_USER') || '';
const password = localStorage.getItem('TELESEC_COUCH_PASS') || ''; const password = localStorage.getItem('TELESEC_COUCH_PASS') || '';
const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || undefined; const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || undefined;
const secret = localStorage.getItem('TELESEC_secret') || ''; // Load saved secret into global SECRET for encryption/decryption
try { SECRET = localStorage.getItem('TELESEC_SECRET') || ''; } catch (e) { SECRET = ''; }
// Call init but don't await; DB functions are safe-guarded with ensureLocal() // Call init but don't await; DB functions are safe-guarded with ensureLocal()
DB.init({ secret, remoteServer, username, password, dbname }).catch((e) => { DB.init({ remoteServer, username, password, dbname }).catch((e) => {
console.warn('DB.autoInit error', e); console.warn('DB.autoInit error', e);
}); });
} catch (e) { } catch (e) {

View File

@@ -134,6 +134,7 @@ PAGES.aulas = {
TS_IndexElement( TS_IndexElement(
"aulas,solicitudes", "aulas,solicitudes",
[ [
{"type": "_encrypted"},
{ {
key: "Solicitante", key: "Solicitante",
type: "persona", type: "persona",
@@ -243,6 +244,7 @@ PAGES.aulas = {
TS_IndexElement( TS_IndexElement(
"aulas,informes", "aulas,informes",
[ [
{"type": "_encrypted"},
{ {
key: "Autor", key: "Autor",
type: "persona", type: "persona",

View File

@@ -173,6 +173,7 @@ PAGES.avisos = {
TS_IndexElement( TS_IndexElement(
"avisos", "avisos",
[ [
{"type": "_encrypted"},
{ {
key: "Origen", key: "Origen",
type: "persona", type: "persona",

View File

@@ -87,6 +87,7 @@ PAGES.comedor = {
TS_IndexElement( TS_IndexElement(
"comedor", "comedor",
[ [
{"type": "_encrypted"},
{ {
key: "Fecha", key: "Fecha",
type: "raw", type: "raw",

View File

@@ -7,6 +7,7 @@ PAGES.login = {
var field_couch_dbname = safeuuid(); var field_couch_dbname = safeuuid();
var field_couch_user = safeuuid(); var field_couch_user = safeuuid();
var field_couch_pass = safeuuid(); var field_couch_pass = safeuuid();
var field_secret = safeuuid();
var btn_save = safeuuid(); var btn_save = safeuuid();
container.innerHTML = ` container.innerHTML = `
<h1>Configuración del servidor CouchDB</h1> <h1>Configuración del servidor CouchDB</h1>
@@ -23,6 +24,9 @@ PAGES.login = {
<label>Contraseña <label>Contraseña
<input type="password" id="${field_couch_pass}" value="${localStorage.getItem('TELESEC_COUCH_PASS') || ''}"><br><br> <input type="password" id="${field_couch_pass}" value="${localStorage.getItem('TELESEC_COUCH_PASS') || ''}"><br><br>
</label> </label>
<label>Clave de encriptación (opcional) - usada para cifrar datos en reposo
<input type="password" id="${field_secret}" value="${localStorage.getItem('TELESEC_SECRET') || ''}"><br><br>
</label>
<button id="${btn_save}" class="btn5">Guardar y Conectar</button> <button id="${btn_save}" class="btn5">Guardar y Conectar</button>
</fieldset> </fieldset>
<p>Después de guardar, el navegador intentará sincronizar en segundo plano con el servidor.</p> <p>Después de guardar, el navegador intentará sincronizar en segundo plano con el servidor.</p>
@@ -32,10 +36,13 @@ PAGES.login = {
var dbname = document.getElementById(field_couch_dbname).value.trim(); var dbname = document.getElementById(field_couch_dbname).value.trim();
var user = document.getElementById(field_couch_user).value.trim(); var user = document.getElementById(field_couch_user).value.trim();
var pass = document.getElementById(field_couch_pass).value; var pass = document.getElementById(field_couch_pass).value;
var secret = document.getElementById(field_secret).value || '';
localStorage.setItem('TELESEC_COUCH_URL', "https://" + url); localStorage.setItem('TELESEC_COUCH_URL', "https://" + url);
localStorage.setItem('TELESEC_COUCH_DBNAME', dbname); localStorage.setItem('TELESEC_COUCH_DBNAME', dbname);
localStorage.setItem('TELESEC_COUCH_USER', user); localStorage.setItem('TELESEC_COUCH_USER', user);
localStorage.setItem('TELESEC_COUCH_PASS', pass); localStorage.setItem('TELESEC_COUCH_PASS', pass);
localStorage.setItem('TELESEC_SECRET', secret);
SECRET = secret;
try { try {
DB.init({ secret: SECRET, remoteServer: "https://" + url, username: user, password: pass, dbname: dbname || undefined }); DB.init({ secret: SECRET, remoteServer: "https://" + url, username: user, password: pass, dbname: dbname || undefined });
toastr.success('Iniciando sincronización con CouchDB'); toastr.success('Iniciando sincronización con CouchDB');

View File

@@ -136,6 +136,7 @@ PAGES.materiales = {
`; `;
const config = [ const config = [
{"type": "_encrypted"},
{ key: "Revision", label: "Ult. Revisión", type: "fecha-diff", default: "" }, { key: "Revision", label: "Ult. Revisión", type: "fecha-diff", default: "" },
{ key: "Nombre", label: "Nombre", type: "text", default: "" }, { key: "Nombre", label: "Nombre", type: "text", default: "" },
{ key: "Ubicacion", label: "Ubicación", type: "text", default: "--" }, { key: "Ubicacion", label: "Ubicación", type: "text", default: "--" },

View File

@@ -111,6 +111,7 @@ PAGES.notas = {
TS_IndexElement( TS_IndexElement(
"notas", "notas",
[ [
{"type": "_encrypted"},
{ {
key: "Autor", key: "Autor",
type: "persona-nombre", type: "persona-nombre",

View File

@@ -900,6 +900,7 @@ PAGES.pagos = {
var totals = { ingresos: 0, gastos: 0 }; var totals = { ingresos: 0, gastos: 0 };
const config = [ const config = [
{"type": "_encrypted"},
{ {
key: "Fecha", key: "Fecha",
label: "Fecha/Hora", label: "Fecha/Hora",

View File

@@ -203,6 +203,7 @@ PAGES.personas = {
`; `;
const config = [ const config = [
{"type": "_encrypted"},
// { // {
// label: "Persona", // label: "Persona",
// type: "persona", // type: "persona",